1use strum::{Display, EnumCount, EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
4
5use crate::{BlockGraph, ModuleCertificationLimits, ModuleError, parse_inline_graph_with_limits};
6
7#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Display, EnumString)]
9#[strum(ascii_case_insensitive)]
10pub enum GalleryCategory {
11 #[strum(to_string = "clifford")]
13 Clifford,
14 #[strum(to_string = "non_clifford")]
16 NonClifford,
17 #[strum(to_string = "factory")]
19 Factory,
20 #[strum(to_string = "external_resource")]
22 ExternalResource,
23 #[strum(to_string = "adaptive")]
25 Adaptive,
26 #[strum(to_string = "arithmetic")]
28 Arithmetic,
29 #[strum(to_string = "addition")]
31 Addition,
32 #[strum(to_string = "analysis_only")]
34 AnalysisOnly,
35}
36
37#[derive(Debug, Copy, Clone)]
39pub struct GalleryEntry {
40 pub(crate) description: &'static str,
41 pub(crate) categories: &'static [GalleryCategory],
42 pub(crate) blog: &'static str,
43}
44
45impl GalleryEntry {
46 pub fn description(&self) -> &'static str {
48 self.description
49 }
50
51 pub fn categories(&self) -> &'static [GalleryCategory] {
53 self.categories
54 }
55
56 pub fn blog(&self) -> &'static str {
58 self.blog
59 }
60}
61
62macro_rules! gallery {
63 ($( $(#[$metadata:meta])* $variant:ident => ($description:literal, $categories:expr, $source:literal $(,)?), )*) => {
64 #[derive(
68 Debug, Copy, Clone, PartialEq, Eq, Hash, Display, EnumCount, EnumIter, EnumString,
69 IntoStaticStr,
70 )]
71 #[repr(usize)]
72 #[strum(ascii_case_insensitive)]
73 pub enum GalleryItem {
74 $(
75 #[doc = $description]
76 $(#[$metadata])*
77 $variant,
78 )*
79 }
80
81 impl GalleryItem {
82 pub fn entry(self) -> &'static GalleryEntry {
84 match self {
85 $(Self::$variant => &GalleryEntry {
86 description: $description,
87 categories: $categories,
88 blog: include_str!($source),
89 },)*
90 }
91 }
92 }
93 };
94}
95
96gallery! {
97 #[strum(to_string = "cnot")]
98 CNOT => ("CNOT gate", CLIFFORD, "../assets/cnot.blog"),
99 #[strum(to_string = "cz_spatial_h")]
100 CZSpatialH => ("CZ gate with a spatial Hadamard.", CLIFFORD, "../assets/cz_spatial_h.blog"),
101 #[strum(to_string = "cz_temporal_h")]
102 CZTemporalH => ("CZ gate with a temporal Hadamard.", CLIFFORD, "../assets/cz_temporal_h.blog"),
103 #[strum(to_string = "s_gate")]
104 S => ("S gate teleportation.", CLIFFORD, "../assets/s.blog"),
105 #[strum(to_string = "t_gate")]
106 T => ("T gate teleportation.", NON_CLIFFORD, "../assets/t.blog"),
107 #[strum(to_string = "t_with_prepared_y")]
108 TWithPreparedY => (
109 "T gate teleportation with a prepared Y for Clifford fix.",
110 NON_CLIFFORD,
111 "../assets/t_with_prepared_y.blog",
112 ),
113 #[strum(to_string = "t_comparison")]
114 TComparison => (
115 "Naive T-state comparison with a feed-forward X/Y parity check.",
116 NON_CLIFFORD,
117 "../assets/t_comparison.blog",
118 ),
119 #[strum(to_string = "phase_gradient", serialize = "phase_gradient_k4")]
120 PhaseGradientK4 => (
121 "Fixed single-qubit sequence of 22 signed pi/4 X/Z rotations and a final H, composed from T-injection modules.",
122 NON_CLIFFORD,
123 "../assets/phase_gradient_k4.blog",
124 ),
125 #[strum(to_string = "and_4t")]
126 And4T => (
127 "AND gate (x, y -> x, y, x AND y) using four T injections, multiplex controls, and a spatial output.",
128 NON_CLIFFORD,
129 "../assets/and_4t.blog",
130 ),
131 #[strum(to_string = "ccz_injected_and")]
132 CCZInjectedAnd => (
133 "Compact CCZ-injected temporary AND with multiplex controls and one temporal Hadamard; based on Low et al., Fig. 43(b), arXiv:2605.30455v1. Three open ports accept |CCZ>.",
134 ADDITION_RESOURCE,
135 "../assets/ccz_injected_and.blog",
136 ),
137 #[strum(to_string = "ccz_injected_maj")]
138 CCZInjectedMaj => (
139 "SAT-synthesized CCZ-injected MAJ carry slice from Low et al., Figs. 42(b), 43(c), arXiv:2605.30455v1; three open ports accept |CCZ>.",
140 ADDITION_RESOURCE,
141 "../assets/ccz_injected_maj.blog",
142 ),
143 #[strum(to_string = "uma")]
144 UMA => (
145 "SAT-synthesized UMA carry-uncompute and sum-extraction slice from Low et al., Figs. 42(c), 43(c), and 62, arXiv:2605.30455v1.",
146 &[
147 GalleryCategory::Clifford,
148 GalleryCategory::Adaptive,
149 GalleryCategory::Arithmetic,
150 GalleryCategory::Addition,
151 ],
152 "../assets/uma.blog",
153 ),
154 #[strum(to_string = "three_bit_adder", serialize = "controlled_adder_3")]
155 ThreeBitAdder => (
156 "Three-bit controlled-adder layout with compact routing, five external CCZ states, and adaptive final CZ corrections.",
157 ADDITION_RESOURCE,
158 "../assets/three_bit_adder.blog",
159 ),
160 #[strum(to_string = "ten_bit_adder", serialize = "controlled_adder_10")]
161 TenBitAdder => (
162 "Ten-bit controlled-adder layout with compact routing, nineteen external CCZ states, and adaptive final CZ corrections.",
163 ADDITION_RESOURCE,
164 "../assets/ten_bit_adder.blog",
165 ),
166 #[strum(to_string = "toffoli_from_and_delayed_cz", serialize = "toffoli")]
167 ToffoliFromAndDelayedCZ => (
168 "Toffoli gate (x, y, z -> x, y, z XOR xy) from the four-T AND gadget with a delayed CZ.",
169 NON_CLIFFORD,
170 "../assets/toffoli_from_and_delayed_cz.blog",
171 ),
172 #[strum(to_string = "ccz_gate_teleport")]
173 CCZGateTeleport => (
174 "CCZ gate teleportation with three explicit correction branches.",
175 EXTERNAL_RESOURCE_NON_CLIFFORD,
176 "../assets/ccz_gate_teleport.blog",
177 ),
178 #[strum(to_string = "ccz_4x3x7_tels")]
179 CCZFactoryWithTels => (
180 "CCZ state factory constructed in arXiv:2409.17595.",
181 FACTORY_NON_CLIFFORD,
182 "../assets/ccz_4x3x7_tels.blog",
183 ),
184 #[strum(to_string = "ccz_4x3x6")]
185 CCZ4x3_6 => (
186 "CCZ state factory, 4x3 layout, without the TELS check.",
187 FACTORY_NON_CLIFFORD,
188 "../assets/ccz_4x3x6.blog",
189 ),
190 #[strum(to_string = "bell_state")]
191 BellState => ("Bell state preparation.", CLIFFORD, "../assets/bell_state.blog"),
192 #[strum(to_string = "ghz")]
193 GHZ => ("GHZ state preparation.", CLIFFORD, "../assets/ghz.blog"),
194 #[strum(to_string = "ghz_slide_then_glide")]
195 GHZSlideThenGlide => (
196 "GHZ state preparation with sliding then gliding blocks.",
197 CLIFFORD,
198 "../assets/ghz_slide_then_glide.blog",
199 ),
200 #[strum(to_string = "ghz_patch_rotations")]
201 GHZPatchRotations => (
202 "GHZ state preparation with per-qubit patch rotations.",
203 CLIFFORD,
204 "../assets/ghz_patch_rotations.blog",
205 ),
206 #[strum(to_string = "1d-yoked", serialize = "1d_yoked")]
207 OneDYoked => ("1D yoked lattice-surgery layout.", CLIFFORD, "../assets/1d_yoked.blog"),
208 #[strum(to_string = "thth")]
209 THTH => ("'-T-H-T-H-' gate sequence", NON_CLIFFORD, "../assets/thth.blog"),
210 #[strum(to_string = "three_cnots")]
211 ThreeCNOTs => ("Compressed three CNOTs.", CLIFFORD, "../assets/three_cnots.blog"),
212 #[strum(to_string = "steane_encoding")]
213 SteaneEncoding => (
214 "Compressed Steane encoding circuit.",
215 CLIFFORD,
216 "../assets/steane_encoding.blog",
217 ),
218 #[strum(to_string = "x_memory")]
219 XMemory => ("X-basis memory experiment.", CLIFFORD, "../assets/x_memory.blog"),
220 #[strum(to_string = "y_memory")]
221 YMemory => ("Y-basis memory experiment.", CLIFFORD, "../assets/y_memory.blog"),
222 #[strum(to_string = "move_rotation")]
223 MoveRotation => (
224 "Rotate boundary types by moving in spacetime.",
225 CLIFFORD,
226 "../assets/move_rotation.blog",
227 ),
228 #[strum(to_string = "stability")]
229 Stability => ("Stability experiment", CLIFFORD, "../assets/stability.blog"),
230}
231
232impl GalleryItem {
233 pub fn build(self) -> BlockGraph {
242 self.build_with_limits(ModuleCertificationLimits::DEFAULT)
243 .expect("embedded gallery .blog files are valid")
244 }
245
246 pub fn build_with_limits(
252 self,
253 limits: ModuleCertificationLimits,
254 ) -> Result<BlockGraph, ModuleError> {
255 parse_inline_graph_with_limits(self.entry().blog, limits)
256 }
257
258 pub fn iter() -> impl Iterator<Item = GalleryItem> {
260 <Self as IntoEnumIterator>::iter()
261 }
262
263 pub fn iter_by_category(category: GalleryCategory) -> impl Iterator<Item = GalleryItem> {
265 Self::iter().filter(move |entry| entry.in_category(category))
266 }
267
268 pub fn id(self) -> &'static str {
270 self.into()
273 }
274
275 pub fn description(self) -> &'static str {
277 self.entry().description
278 }
279
280 pub fn categories(self) -> &'static [GalleryCategory] {
282 self.entry().categories
283 }
284
285 pub fn in_category(self, category: GalleryCategory) -> bool {
287 self.categories().contains(&category)
288 }
289}
290
291const CLIFFORD: &[GalleryCategory] = &[GalleryCategory::Clifford];
292const NON_CLIFFORD: &[GalleryCategory] = &[GalleryCategory::NonClifford];
293const FACTORY_NON_CLIFFORD: &[GalleryCategory] =
294 &[GalleryCategory::Factory, GalleryCategory::NonClifford];
295const EXTERNAL_RESOURCE_NON_CLIFFORD: &[GalleryCategory] = &[
296 GalleryCategory::ExternalResource,
297 GalleryCategory::NonClifford,
298];
299const ADDITION_RESOURCE: &[GalleryCategory] = &[
300 GalleryCategory::ExternalResource,
301 GalleryCategory::NonClifford,
302 GalleryCategory::Adaptive,
303 GalleryCategory::Arithmetic,
304 GalleryCategory::Addition,
305];
306
307#[cfg(test)]
308mod tests {
309 use std::collections::HashMap;
310
311 use bloq_utils::Pauli;
312
313 use super::*;
314 use crate::{Action, Block, Expr, Pipe};
315 use bloq_utils::Direction;
316 use glam::IVec3;
317
318 const GATE_GADGETS: [GalleryItem; 5] = [
319 GalleryItem::CNOT,
320 GalleryItem::S,
321 GalleryItem::T,
322 GalleryItem::THTH,
323 GalleryItem::ToffoliFromAndDelayedCZ,
324 ];
325
326 #[test]
327 fn test_galleries_validity() {
328 for entry in GalleryItem::iter() {
329 let source = entry.build();
330 let graph = source
331 .materialize_root_graph()
332 .expect("gallery flat projection");
333 let validation = if entry.in_category(GalleryCategory::AnalysisOnly)
334 || !source.instances.is_empty()
335 {
336 graph.validate_source()
337 } else {
338 graph.validate()
339 };
340 validation.unwrap_or_else(|err| {
341 panic!("gallery entry {:?} failed validation: {}", entry.id(), err)
342 });
343 }
344 }
345
346 #[test]
347 fn composed_sources_keep_reusable_module_hierarchies() {
348 let adder = crate::BlockGraph::from_text(bloq_test::ONE_BIT_ADDER_SOURCE).unwrap();
349 assert_eq!(adder.modules().len(), 4);
350 assert_eq!(adder.root().instances.len(), 3);
351 assert_eq!(adder.root().quantum_connections.len(), 18);
352 assert_eq!(
353 adder
354 .direct_pipe_endpoints("main")
355 .expect("adder root exists")
356 .len(),
357 4
358 );
359
360 let phase = GalleryItem::PhaseGradientK4.build();
361 assert_eq!(phase.modules().len(), 5);
362 assert_eq!(phase.root().instances.len(), 22);
363 assert!(phase.module("FinalHadamard").is_none());
364 assert!(phase.root().local_body().has_block_at(IVec3::new(0, 0, 23)));
365 assert!(phase.root().quantum_connections.iter().any(|connection| {
366 matches!(
367 connection,
368 crate::QuantumConnection::Output {
369 block,
370 hadamard: true,
371 ..
372 } if *block == IVec3::new(0, 0, 23)
373 )
374 }));
375 assert_eq!(
376 phase
377 .direct_pipe_endpoints("main")
378 .expect("phase-gradient root exists")
379 .len(),
380 21
381 );
382 }
383
384 #[test]
385 fn addition_gallery_keeps_the_compact_recipe_and_register_interfaces() {
386 for item in [
387 GalleryItem::CCZInjectedAnd,
388 GalleryItem::CCZInjectedMaj,
389 GalleryItem::UMA,
390 GalleryItem::ThreeBitAdder,
391 GalleryItem::TenBitAdder,
392 ] {
393 assert!(item.in_category(GalleryCategory::Arithmetic));
394 assert!(item.in_category(GalleryCategory::Addition));
395 }
396 let and = GalleryItem::CCZInjectedAnd.build();
397 assert_eq!(and.root().local_body().blocks().count(), 38);
398 for (item, bits, blocks) in [
399 (GalleryItem::ThreeBitAdder, 3, 532),
400 (GalleryItem::TenBitAdder, 10, 1932),
401 ] {
402 let program = item.build();
403 assert_eq!(
404 program
405 .module("InjectedAnd")
406 .unwrap()
407 .local_body()
408 .to_blog_body_text(),
409 and.root().local_body().to_blog_body_text()
410 );
411 assert_eq!(
412 program
413 .extract_definition("InjectedAnd")
414 .unwrap()
415 .to_blog_text(),
416 and.to_blog_text()
417 );
418 assert_eq!(program.root().instances.len(), 3 * bits - 1);
419 assert_eq!(
420 program
421 .root()
422 .interface
423 .quantum_ports
424 .iter()
425 .filter(|port| port.resource_type == "ccz")
426 .count(),
427 3 * (2 * bits - 1)
428 );
429 assert_eq!(
430 program
431 .root()
432 .interface
433 .quantum_ports
434 .iter()
435 .filter(|port| port.direction == crate::PortDirection::Output)
436 .count(),
437 2 * bits + 1
438 );
439 let graph = program.materialize_flat_graph().unwrap();
440 assert_eq!(graph.blocks().count(), blocks);
441 assert_eq!(*graph.spans().unwrap().2.end(), 14);
442 }
443 }
444
445 #[test]
451 fn every_composable_gallery_port_admits_every_pauli() {
452 for entry in GalleryItem::iter() {
453 if entry.in_category(GalleryCategory::AnalysisOnly)
457 || !entry.build().root().instances.is_empty()
458 || matches!(
459 entry,
460 GalleryItem::CCZInjectedAnd | GalleryItem::CCZInjectedMaj | GalleryItem::UMA
461 )
462 {
463 continue;
464 }
465 let graph = entry
466 .build()
467 .materialize_root_graph()
468 .expect("gallery flat projection");
469 for projection in graph.branch_projections().unwrap() {
470 let graph = projection.graph();
471 let stabilizers = graph.stabilizers().unwrap();
472
473 for block in graph.blocks().filter(|block| block.kind().is_port()) {
474 let mut span = Vec::new();
475 for generator in &stabilizers.generators {
476 let mut bits = *generator
477 .stabilizer
478 .port_stabilizer
479 .get(&block.pos())
480 .unwrap_or(&Pauli::I) as u8;
481 for basis in &span {
482 bits = bits.min(bits ^ basis);
483 }
484 if bits != 0 {
485 span.push(bits);
486 span.sort_unstable_by(|lhs, rhs| rhs.cmp(lhs));
487 }
488 }
489 assert_eq!(
490 span.len(),
491 2,
492 "{} {:?} port {} spans only {:?}",
493 entry.id(),
494 projection.assignments(),
495 block.pos(),
496 span
497 );
498 }
499 }
500 }
501 }
502
503 #[test]
506 fn gate_gadget_interfaces_are_jointly_transparent_in_every_branch() {
507 for entry in GATE_GADGETS {
508 let graph = entry
509 .build()
510 .materialize_root_graph()
511 .expect("gallery flat projection");
512 let stabilizers = graph.stabilizers().unwrap();
513 let zx = &stabilizers.zx_graph;
514 let rows = stabilizers
515 .generators
516 .iter()
517 .map(|generator| &generator.stabilizer.paulis)
518 .chain(stabilizers.auxiliary_rows())
519 .collect::<Vec<_>>();
520 let (outputs, inputs): (Vec<usize>, Vec<usize>) = zx
521 .nodes()
522 .iter()
523 .filter(|node| node.kind.is_port())
524 .map(|node| node.id)
525 .partition(|&id| zx.nodes()[id].is_output_port(zx));
526 assert!(
527 !inputs.is_empty() && !outputs.is_empty(),
528 "{} ports",
529 entry.id()
530 );
531
532 let resolves = graph
533 .actions()
534 .iter()
535 .filter_map(|action| match action {
536 Action::Resolve { target, .. } => {
537 let node = zx
538 .nodes()
539 .iter()
540 .find(|node| node.pos == *target)
541 .expect("a resolve targets a node");
542 let crate::NodeKind::Selective(kind) = node.kind else {
543 panic!("a resolve targets a selective")
544 };
545 Some((node.id, kind, *target))
546 }
547 _ => None,
548 })
549 .collect::<Vec<_>>();
550 let targets = resolves
551 .iter()
552 .map(|&(_, _, target)| target)
553 .collect::<Vec<_>>();
554 let domain = graph.action_graph().resolve_value_domain(&targets).unwrap();
555 assert!(!domain.values().is_empty(), "{} has no branch", entry.id());
556 for values in domain.values() {
557 let caps = resolves
558 .iter()
559 .zip(values)
560 .map(|(&(col, kind, _), &value)| {
561 let basis = if value {
562 kind.pauli_if_true()
563 } else {
564 kind.pauli_if_false()
565 };
566 (col, Pauli::from(basis))
567 })
568 .collect::<Vec<_>>();
569 for (side, ports) in [("input", &inputs), ("output", &outputs)] {
570 let rank = admissible_interface_rank(&rows, &caps, ports);
571 assert_eq!(
572 rank,
573 2 * ports.len(),
574 "{} {side} interface is rank {rank} of {} when the caps read {caps:?}",
575 entry.id(),
576 2 * ports.len()
577 );
578 }
579 }
580 }
581 }
582
583 #[test]
585 fn gate_gadget_records_close_below_their_outputs() {
586 for entry in GATE_GADGETS {
587 let graph = entry
588 .build()
589 .materialize_root_graph()
590 .expect("gallery flat projection");
591 let stabilizers = graph.stabilizers().unwrap();
592 let zx = &stabilizers.zx_graph;
593 for generator in &stabilizers.generators {
594 let Some(name) = generator.measurement_name() else {
595 continue;
596 };
597 let reached = zx
598 .nodes()
599 .iter()
600 .filter(|node| node.is_output_port(zx))
601 .filter(|node| generator.stabilizer.paulis.get(node.id) != Pauli::I)
602 .map(|node| node.pos)
603 .collect::<Vec<_>>();
604 assert!(
605 reached.is_empty(),
606 "{} record {name} reaches output ports {reached:?}",
607 entry.id()
608 );
609 }
610 }
611 }
612
613 #[test]
615 fn universal_gate_gadget_fixings_close_off_their_ports() {
616 for &entry in &GATE_GADGETS[..4] {
617 let stabilizers = entry
618 .build()
619 .materialize_root_graph()
620 .expect("gallery flat projection")
621 .stabilizers()
622 .unwrap();
623 let zx = &stabilizers.zx_graph;
624 for generator in &stabilizers.generators {
625 if generator.kind.is_selective_fixing() {
626 assert!(
627 zx.nodes()
628 .iter()
629 .filter(|node| node.kind.is_port())
630 .all(|node| generator.stabilizer.paulis.get(node.id) == Pauli::I),
631 "{} fixing reaches a port",
632 entry.id()
633 );
634 }
635 }
636 }
637 }
638
639 #[test]
641 fn stacked_gadgets_keep_deadlines_and_forward_dependencies() {
642 let stacks: &[&[GalleryItem]] = &[
643 &[GalleryItem::S, GalleryItem::T],
644 &[GalleryItem::S, GalleryItem::THTH],
645 &[GalleryItem::T, GalleryItem::S],
646 &[GalleryItem::T, GalleryItem::THTH],
647 &[GalleryItem::THTH, GalleryItem::S],
648 &[GalleryItem::THTH, GalleryItem::T],
649 &[GalleryItem::S, GalleryItem::T, GalleryItem::THTH],
650 &[GalleryItem::S, GalleryItem::THTH, GalleryItem::T],
651 &[GalleryItem::T, GalleryItem::S, GalleryItem::THTH],
652 &[GalleryItem::T, GalleryItem::THTH, GalleryItem::S],
653 &[GalleryItem::THTH, GalleryItem::S, GalleryItem::T],
654 &[GalleryItem::THTH, GalleryItem::T, GalleryItem::S],
655 ];
656 for &stack in stacks {
657 let label = stack
658 .iter()
659 .map(|item| item.id().to_string())
660 .collect::<Vec<_>>()
661 .join(" -> ");
662 let mut composite = stack[0]
663 .build()
664 .materialize_root_graph()
665 .expect("gallery flat projection");
666 let mut deadlines = record_deadlines(&composite);
667 let mut stage_of = vec![0usize; composite.actions().len()];
668 for (stage, &item) in stack.iter().enumerate().skip(1) {
669 let upper = item
670 .build()
671 .materialize_root_graph()
672 .expect("gallery flat projection");
673 let shift = stack_offset(&composite, &upper);
674 deadlines.extend(
675 record_deadlines(&upper)
676 .into_iter()
677 .map(|(name, top)| (name, top + shift.z)),
678 );
679 stage_of.extend(std::iter::repeat_n(stage, upper.actions().len()));
680 composite = stack_in_time(composite, upper.shift_positions(shift).unwrap());
681 }
682
683 composite
684 .validate()
685 .unwrap_or_else(|err| panic!("{label}: {err}"));
686 let (composite, stabilizers) = composite
687 .analyze_actions()
688 .unwrap_or_else(|err| panic!("{label}: {err}"));
689 let zx = &stabilizers.zx_graph;
690 for generator in &stabilizers.generators {
691 let Some(name) = generator.measurement_name() else {
692 continue;
693 };
694 let stabilizer = &generator.stabilizer;
695 assert!(
696 zx.nodes()
697 .iter()
698 .filter(|node| node.is_output_port(zx))
699 .all(|node| stabilizer.paulis.get(node.id) == Pauli::I),
700 "{label}: {name} reaches an output port"
701 );
702 assert!(
703 support_top(stabilizer) <= deadlines[name],
704 "{label}: {name} decodes at layer {} instead of {}",
705 support_top(stabilizer),
706 deadlines[name]
707 );
708 }
709 for (from, to, kind) in composite.action_graph().dependencies() {
710 assert!(
711 stage_of[from] <= stage_of[to],
712 "{label}: {kind:?} dependency {from} -> {to} points back in time"
713 );
714 }
715 }
716 }
717
718 #[test]
720 fn repeated_t_gadgets_compose_after_alpha_renaming() {
721 let lower = GalleryItem::T
722 .build()
723 .materialize_root_graph()
724 .expect("gallery flat projection");
725 let upper = GalleryItem::T
726 .build()
727 .materialize_root_graph()
728 .expect("gallery flat projection");
729 let shift = stack_offset(&lower, &upper);
730 let mut upper = upper.shift_positions(shift).unwrap();
731 upper
732 .set_actions(
733 upper
734 .actions()
735 .into_iter()
736 .map(|action| match action {
737 Action::Measure { target, .. } => Action::Measure {
738 target,
739 name: "mzz_upper".into(),
740 },
741 Action::Resolve { target, .. } => Action::Resolve {
742 target,
743 condition: Expr::Var("mzz_upper".into()),
744 },
745 action => action,
746 })
747 .collect(),
748 )
749 .unwrap();
750 let composite = stack_in_time(lower, upper);
751
752 composite.validate().unwrap();
753 composite.analyze_actions().unwrap();
754 }
755
756 fn support_top(stabilizer: &crate::Stabilizer) -> i32 {
758 let nodes = stabilizer
759 .interior_nodes
760 .iter()
761 .filter(|&(_, &pauli)| pauli != Pauli::I)
762 .map(|(pos, _)| pos.z);
763 let edges = stabilizer
764 .interior_edges
765 .iter()
766 .filter(|&(_, &pauli)| pauli != Pauli::I)
767 .flat_map(|(&(from, to), _)| [from.z, to.z]);
768 nodes.chain(edges).max().expect("a surface has support")
769 }
770
771 fn record_deadlines(graph: &BlockGraph) -> HashMap<String, i32> {
772 graph
773 .stabilizers()
774 .unwrap()
775 .generators
776 .iter()
777 .filter_map(|generator| {
778 let name = generator.measurement_name()?;
779 Some((name.to_owned(), support_top(&generator.stabilizer)))
780 })
781 .collect()
782 }
783
784 fn stack_offset(lower: &BlockGraph, upper: &BlockGraph) -> IVec3 {
786 let lower_out = output_port(lower);
787 let upper_in = upper
788 .blocks()
789 .filter(|block| block.kind().is_port())
790 .min_by_key(|block| block.pos().z)
791 .expect("an input port")
792 .pos();
793 lower_out + IVec3::Z - upper_in
794 }
795
796 fn output_port(graph: &BlockGraph) -> IVec3 {
797 graph
798 .blocks()
799 .filter(|block| block.kind().is_port())
800 .max_by_key(|block| block.pos().z)
801 .expect("an output port")
802 .pos()
803 }
804
805 fn stack_in_time(mut lower: BlockGraph, upper: BlockGraph) -> BlockGraph {
807 let seam_low = output_port(&lower);
808 let seam_high = seam_low + IVec3::Z;
809 let low_kind = lower
810 .get_block(seam_low - IVec3::Z)
811 .expect("the cube under the output port")
812 .kind();
813 let high_kind = upper
814 .get_block(seam_high + IVec3::Z)
815 .expect("the cube over the input port")
816 .kind();
817
818 lower.remove_block(seam_low);
820 lower.try_add_block(Block::new(seam_low, low_kind)).unwrap();
821 lower
822 .try_add_pipe(Pipe::new(seam_low - IVec3::Z, Direction::ZPLUS))
823 .unwrap();
824 for block in upper.blocks() {
825 let block = if block.pos() == seam_high {
826 Block::new(seam_high, high_kind)
827 } else {
828 block.clone()
829 };
830 lower.try_add_block(block).unwrap();
831 }
832 for pipe in upper.pipes() {
833 lower.try_add_pipe(pipe.clone()).unwrap();
834 }
835 lower
836 .try_add_pipe(Pipe::new(seam_low, Direction::ZPLUS))
837 .unwrap();
838 let mut actions = lower.actions();
841 actions.extend(upper.actions());
842 lower.set_actions(actions).unwrap();
843 lower
844 }
845
846 fn admissible_interface_rank(
850 rows: &[&bloq_utils::PauliString],
851 caps: &[(usize, Pauli)],
852 ports: &[usize],
853 ) -> usize {
854 let anticommutes = |lhs: Pauli, rhs: Pauli| {
855 let (lhs, rhs) = (lhs as u8, rhs as u8);
856 ((lhs & 1) & (rhs >> 1)) ^ ((lhs >> 1) & (rhs & 1)) == 1
857 };
858 assert!(caps.len() + 2 * ports.len() <= u64::BITS as usize);
859
860 let mut basis = [0u64; u64::BITS as usize];
861 for row in rows {
862 let mut vector = 0u64;
863 for (bit, &(col, cap)) in caps.iter().enumerate() {
864 vector |= u64::from(anticommutes(row.get(col), cap)) << bit;
865 }
866 for (index, &col) in ports.iter().enumerate() {
867 vector |= u64::from(row.get(col) as u8) << (caps.len() + 2 * index);
868 }
869 while vector != 0 {
870 let pivot = &mut basis[vector.trailing_zeros() as usize];
871 if *pivot == 0 {
872 *pivot = vector;
873 break;
874 }
875 vector ^= *pivot;
876 }
877 }
878 basis[caps.len()..]
879 .iter()
880 .filter(|&&vector| vector != 0)
881 .count()
882 }
883
884 #[test]
885 fn test_flip_xz_basis_revalidates_all_gallery_entries() {
886 for entry in GalleryItem::iter() {
887 let source = entry.build();
888 let graph = source
889 .materialize_root_graph()
890 .expect("gallery flat projection");
891 let source_only =
892 entry.in_category(GalleryCategory::AnalysisOnly) || !source.instances.is_empty();
893 let flipped = if source_only {
894 graph.flip_xz_basis_lenient().unwrap()
895 } else {
896 graph
897 .flip_xz_basis()
898 .unwrap_or_else(|err| panic!("{} flip_xz_basis failed: {}", entry.id(), err))
899 };
900 let validation = if source_only {
901 flipped.validate_source()
902 } else {
903 flipped.validate()
904 };
905 validation.unwrap_or_else(|err| {
906 panic!("{} flipped graph became invalid: {}", entry.id(), err)
907 });
908 }
909 }
910
911 #[test]
912 fn test_gallery_ids_roundtrip() {
913 assert_eq!(
914 "one_bit_adder".parse::<GalleryItem>(),
915 Err(strum::ParseError::VariantNotFound)
916 );
917 for entry in GalleryItem::iter() {
918 assert_eq!(
919 entry
920 .id()
921 .parse::<GalleryItem>()
922 .unwrap_or_else(|err| panic!(
923 "gallery id {:?} failed to roundtrip: {}",
924 entry.id(),
925 err
926 )),
927 entry
928 );
929 }
930 }
931}