1use std::num::NonZeroUsize;
2use std::sync::{Arc, OnceLock};
3use std::time::Duration;
4
5use bloq_graph::{
6 Basis, Block, BlockGraph, BlockKind, ModuleCertificationError, ModuleCertificationLimits,
7 ModuleSummary, PortRole, RuntimeStabilizerBasis, StabilizerGenerators, default_module_jobs,
8};
9use glam::{IVec2, IVec3};
10use web_time::Instant;
11
12use crate::block::{
13 LoweringTemplateId, SelectiveTemplates, SpatialHadamardKey, WallSide, compile_fixed_bulk,
14 compile_multiplex_port, compile_realignment, compile_selective, compile_spatial_hadamard,
15 validate_fixed_bulk,
16};
17use crate::config::CompileConfig;
18use crate::lower::{PhysicalInput, lower_clifford_proxy};
19use crate::signature::{
20 BlockSignature, BlockSignatureMap, block_signatures, derive_block_signature,
21 join_component_layer_schedules, relocatable_block_signatures,
22};
23use crate::spatial_port::{SpatialPortExpansionMap, expand_spatial_ports};
24use crate::{Bloq, CompileError, Connectivity};
25
26#[path = "registry.rs"]
27mod registry;
28
29pub(crate) type CompiledTemplateMap = crate::FxMap<IVec3, CompiledTemplateInfo>;
31type CompiledDefinitionVariantMap =
32 crate::FxMap<IVec3, Vec<(BlockSignature, CompiledTemplateInfo)>>;
33struct ProxyPreflight {
35 graph: BlockGraph,
36 signatures: BlockSignatureMap,
37 stabilizers: StabilizerGenerators,
38 spatial_ports: SpatialPortExpansionMap,
39}
40
41pub const CODE_DISTANCE_METADATA_KEY: &str = "bloq_compile.code_distance";
43pub const CONVENTION_METADATA_KEY: &str = "bloq_compile.convention";
45pub const CLIFFORD_PROXY_SEED_METADATA_KEY: &str = "bloq_compile.clifford_proxy_seed";
47
48fn module_normalization_offset(
54 graph: &BlockGraph,
55 module: &ModuleSummary,
56) -> Result<IVec3, CompileError> {
57 let min_z = graph.spans().map_or(0, |(_, _, span)| *span.start());
58 let z = i32::try_from(-i64::from(min_z)).map_err(|_| {
59 ModuleCertificationError::InvalidConnection {
60 module: module.name().to_string(),
61 message: "module normalization exceeds i32 coordinates".to_string(),
62 }
63 })?;
64 Ok(IVec3::new(0, 0, z))
65}
66
67pub(crate) fn block_xy_offset(block_xy: IVec2, distance: u32) -> Result<IVec2, CompileError> {
70 let stride = 2 * i128::from(distance) + 2;
71 let x = i32::try_from(i128::from(block_xy.x) * stride)
72 .map_err(|_| CompileError::BlockLayoutCoordinateOverflow { block_xy, distance })?;
73 let y = i32::try_from(i128::from(block_xy.y) * stride)
74 .map_err(|_| CompileError::BlockLayoutCoordinateOverflow { block_xy, distance })?;
75 Ok(IVec2::new(x, y))
76}
77
78#[derive(Debug, Clone, Copy)]
81pub(crate) struct BlockLayout {
82 distance: u32,
83}
84
85impl BlockLayout {
86 pub(crate) fn new(distance: u32) -> Self {
87 Self { distance }
88 }
89
90 pub(crate) fn distance(self) -> u32 {
91 self.distance
92 }
93
94 pub(crate) fn offset(self, pos: IVec3) -> Result<IVec2, CompileError> {
95 block_xy_offset(pos.truncate(), self.distance)
96 }
97}
98
99#[derive(Debug, Clone)]
102pub struct CompileArtifacts {
103 pub bloq: Bloq,
105 pub compile_duration: Duration,
107 pub warnings: Vec<&'static str>,
116}
117
118pub struct CompiledObject {
124 config: CompileConfig,
125 artifacts: Arc<CompileArtifacts>,
126}
127
128impl std::fmt::Debug for CompiledObject {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 f.debug_struct("CompiledObject")
131 .field("config", &self.config)
132 .finish_non_exhaustive()
133 }
134}
135
136impl CompiledObject {
137 #[must_use]
139 pub fn config(&self) -> CompileConfig {
140 self.config
141 }
142}
143
144struct PreparedProxy {
146 graph: BlockGraph,
147 stabilizers: StabilizerGenerators,
148 spatial_ports: SpatialPortExpansionMap,
149 compiled: CompiledTemplateMap,
150 spatial_port_templates: CompiledTemplateMap,
151 plan: crate::lower::LinkedPlanInput,
152 temporal_templates: crate::FxMap<crate::lower::TemplatePlan, LoweringTemplateId>,
153 wall_templates: crate::FxMap<crate::lower::SpatialPipeRef, LoweringTemplateId>,
154 padding_templates: crate::padding::PreparedPaddingTemplates,
155 template_pool: crate::block::LoweringTemplatePool,
156}
157
158pub(crate) struct PreparedDefinitionObject {
159 name: String,
160 dependencies: Vec<Arc<PreparedDefinitionObject>>,
161 variants: CompiledDefinitionVariants,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165pub(crate) struct DefinitionVariantKey {
166 local_position: IVec3,
167 signature: BlockSignature,
168 graph_connectivity: Connectivity,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Hash)]
172pub(crate) struct DefinitionObjectCacheKey {
173 implementation: String,
174 orientations: Vec<bloq_graph::ModuleOrientation>,
175}
176
177#[derive(Debug, Clone)]
178struct PreparedSiteRelocation {
179 position: IVec3,
180 definition: usize,
181 local_position: IVec3,
182}
183
184type CompiledDefinitionVariants = crate::FxMap<DefinitionVariantKey, CompiledTemplateInfo>;
185fn build_definition_objects(
186 cache: &crate::cache::TemplateCacheShard,
187 names: &[String],
188 mut parts_by_name: std::collections::HashMap<String, CompiledDefinitionVariants>,
189 mut built: std::collections::HashMap<String, Arc<PreparedDefinitionObject>>,
190 definition_keys: &std::collections::HashMap<String, String>,
191 orientations_by_name: &std::collections::HashMap<String, Vec<bloq_graph::ModuleOrientation>>,
192 program: &BlockGraph,
193) -> Vec<Arc<PreparedDefinitionObject>> {
194 while built.len() < names.len() {
195 let before = built.len();
196 for name in names {
197 if built.contains_key(name) {
198 continue;
199 }
200 let definition = program
201 .module(name)
202 .expect("reachable cache keys name validated definitions");
203 let mut dependency_names = definition
204 .instances
205 .iter()
206 .map(|instance| instance.definition.as_str())
207 .collect::<Vec<_>>();
208 dependency_names.sort_unstable();
209 dependency_names.dedup();
210 if dependency_names
211 .iter()
212 .any(|dependency| !built.contains_key(*dependency))
213 {
214 continue;
215 }
216 let dependencies = dependency_names
217 .into_iter()
218 .map(|dependency| Arc::clone(&built[dependency]))
219 .collect();
220 let variants = parts_by_name
221 .remove(name)
222 .expect("every definition has a retained object or compiled parts");
223 let object = PreparedDefinitionObject {
224 name: name.clone(),
225 dependencies,
226 variants,
227 };
228 let object = cache.insert_definition_object(
229 DefinitionObjectCacheKey {
230 implementation: definition_keys[name].clone(),
231 orientations: orientations_by_name[name].clone(),
232 },
233 Arc::new(object),
234 );
235 built.insert(name.clone(), object);
236 }
237 assert!(
238 built.len() > before,
239 "validated module dependencies are acyclic"
240 );
241 }
242 let definitions = names
243 .iter()
244 .map(|name| Arc::clone(&built[name]))
245 .collect::<Vec<_>>();
246 debug_assert!(definitions.iter().all(|object| {
247 object
248 .dependencies
249 .windows(2)
250 .all(|pair| pair[0].name < pair[1].name)
251 }));
252 definitions
253}
254
255fn relocate_definition_sites(
256 definitions: &[Arc<PreparedDefinitionObject>],
257 linked_positions: impl IntoIterator<Item = IVec3>,
258 module_sites: &std::collections::HashMap<IVec3, bloq_graph::MaterializedModuleSite>,
259) -> Vec<PreparedSiteRelocation> {
260 let definition_by_name = definitions
261 .iter()
262 .enumerate()
263 .map(|(index, definition)| (definition.name.as_str(), index))
264 .collect::<std::collections::HashMap<_, _>>();
265 let mut relocations = linked_positions
266 .into_iter()
267 .map(|position| {
268 let site = &module_sites[&position];
269 PreparedSiteRelocation {
270 position,
271 definition: definition_by_name[site.definition.as_str()],
272 local_position: site.local_position,
273 }
274 })
275 .collect::<Vec<_>>();
276 relocations.sort_unstable_by_key(|site| site.position.to_array());
277 relocations
278}
279
280fn select_link_signatures(
281 config: CompileConfig,
282 graph: &BlockGraph,
283 spatial_ports: &SpatialPortExpansionMap,
284 #[cfg(test)] builds: &std::sync::atomic::AtomicUsize,
285) -> Result<BlockSignatureMap, CompileError> {
286 #[cfg(test)]
287 builds.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
288 let schedules = join_component_layer_schedules(graph)?;
289 let mut signatures = block_signatures(graph, &schedules, config.code_distance())?;
290 for expansion in spatial_ports.values() {
291 let signature = signatures
292 .get_mut(&expansion.source)
293 .expect("signature map contains the derived cube");
294 signature.connectivity = signature.connectivity.with_pipe(expansion.cube_pipe_dir());
295 }
296 for &signature in signatures.values() {
297 validate_fixed_bulk(signature)?;
298 }
299 Ok(signatures)
300}
301
302fn select_module_link_templates_with_schedule(
303 config: CompileConfig,
304 graph: &BlockGraph,
305 spatial_ports: &SpatialPortExpansionMap,
306 definitions: &[Arc<PreparedDefinitionObject>],
307 sites: &[PreparedSiteRelocation],
308 schedules: &crate::signature::LayerScheduleMap,
309 t_sides: &crate::FxMap<IVec3, bloq_graph::Direction>,
310) -> Result<CompiledTemplateMap, CompileError> {
311 sites
312 .iter()
313 .map(|site| {
314 let definition = &definitions[site.definition];
315 let block = graph
316 .get_block(site.position)
317 .expect("module relocation names a linked block");
318 let mut graph_connectivity = BlockSignature::graph_connectivity(block, graph);
319 if let Some(expansion) = spatial_ports.get(&site.position) {
320 graph_connectivity = graph_connectivity.with_pipe(expansion.cube_pipe_dir());
321 }
322 let surgery_side = (block.kind() == BlockKind::T).then(|| t_sides[&site.position]);
323 let mut linked_signature = derive_block_signature(
324 graph,
325 block,
326 config.code_distance(),
327 crate::signature::layer_schedule_at(schedules, site.position),
328 surgery_side,
329 )?;
330 if let Some(expansion) = spatial_ports.get(&site.position) {
331 linked_signature.connectivity = linked_signature
332 .connectivity
333 .with_pipe(expansion.cube_pipe_dir());
334 }
335 let variant = DefinitionVariantKey {
336 local_position: site.local_position,
337 signature: linked_signature,
338 graph_connectivity,
339 };
340 let Some(info) = definition.variants.get(&variant) else {
341 return Err(CompileError::MissingModulePhysicalVariant {
342 module: definition.name.clone(),
343 local_position: site.local_position,
344 linked_position: site.position,
345 signature: format!(
346 "signature={linked_signature:?}, graph={graph_connectivity:?}"
347 ),
348 });
349 };
350 validate_fixed_bulk(variant.signature)?;
351 Ok((site.position, *info))
352 })
353 .collect()
354}
355
356pub fn compile(graph: &BlockGraph, distance: u32) -> Result<Bloq, CompileError> {
381 let config = CompileConfig::try_new(distance)?;
382 Ok(compile_with(graph, config)?.bloq)
383}
384
385pub fn compile_with(
403 graph: &BlockGraph,
404 config: CompileConfig,
405) -> Result<CompileArtifacts, CompileError> {
406 CompileContext::new(config).compile(graph)
407}
408
409#[must_use]
413pub fn compiled_distance(bloq: &Bloq) -> Option<u32> {
414 match bloq.metadata().get(CODE_DISTANCE_METADATA_KEY)? {
415 bloq_ir::MetadataValue::U64(distance) => u32::try_from(*distance).ok(),
416 bloq_ir::MetadataValue::String(_) => None,
417 }
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
423pub(crate) struct CompiledTemplateInfo {
424 pub(crate) graph_connectivity: Connectivity,
425 pub(crate) template: TemplateRef,
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub(crate) enum TemplateRef {
439 Fixed(LoweringTemplateId),
440 Selective {
441 when_true: LoweringTemplateId,
442 when_false: LoweringTemplateId,
443 },
444 T {
445 cultivation: LoweringTemplateId,
446 escape: LoweringTemplateId,
447 },
448}
449
450impl TemplateRef {
451 pub(crate) fn is_selective(self) -> bool {
454 matches!(self, TemplateRef::Selective { .. })
455 }
456
457 pub(crate) fn observable_template(self) -> Option<LoweringTemplateId> {
461 match self {
462 TemplateRef::Fixed(id) => Some(id),
463 TemplateRef::T { escape, .. } => Some(escape),
464 TemplateRef::Selective { .. } => None,
465 }
466 }
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq)]
471pub enum CompileStage {
472 Validation,
474 Certification,
476 Correlations,
478 Placement,
480 Templates,
482 Readouts,
484 Optimization,
486 CacheReuse,
488 Complete,
490}
491
492impl std::fmt::Display for CompileStage {
493 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494 formatter.write_str(match self {
495 Self::Validation => "Validating source",
496 Self::Certification => "Certifying module",
497 Self::Correlations => "Planning correlations",
498 Self::Placement => "Placing physical blocks",
499 Self::Templates => "Binding physical program",
500 Self::Readouts => "Lowering readouts",
501 Self::Optimization => "Finalizing program",
502 Self::CacheReuse => "Reusing cached module",
503 Self::Complete => "Compilation complete",
504 })
505 }
506}
507
508struct ProgressObserver(Box<dyn Fn(CompileStage) + Send + Sync>);
509
510impl std::fmt::Debug for ProgressObserver {
511 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
512 formatter.write_str("ProgressObserver(..)")
513 }
514}
515
516#[derive(Debug)]
524pub struct CompileContext {
525 config: CompileConfig,
526 cache: Arc<crate::cache::TemplateCacheShard>,
527 summaries: Arc<crate::cache::ModuleSummaryCache>,
528 clifford_proxy: Option<CliffordProxyPins>,
529 progress_observer: Option<ProgressObserver>,
530 cancellation: Option<bloq_graph::CancellationToken>,
531 #[cfg(test)]
532 flat_signature_builds: std::sync::atomic::AtomicUsize,
533}
534
535#[derive(Debug)]
538struct CliffordProxyPins {
539 pins: Vec<bool>,
540 purpose: CliffordProxyPurpose,
541 source_summary: Option<Arc<ModuleSummary>>,
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq)]
546enum CliffordProxyPurpose {
547 Distance,
549 Detslice,
552}
553
554pub fn compile_clifford_proxy(
604 config: CompileConfig,
605 graph: &BlockGraph,
606 pins: &[bool],
607) -> Result<CompileArtifacts, CompileError> {
608 compile_proxy(config, graph, pins, CliffordProxyPurpose::Distance)
609}
610
611pub fn compile_random_clifford_proxy(
622 config: CompileConfig,
623 graph: &BlockGraph,
624 seed: u64,
625) -> Result<CompileArtifacts, CompileError> {
626 let flat = flat_proxy_graph(config, graph)?;
627 let source = flat.as_ref().unwrap_or(graph);
628 let pins = random_proxy_pins(source, seed)?;
629 let mut artifacts = compile_pinned_clifford_proxy(
630 config,
631 source,
632 &pins,
633 CliffordProxyPurpose::Distance,
634 graph.has_module_structure().then_some(graph),
635 )?;
636 artifacts.bloq.insert_metadata(
637 CLIFFORD_PROXY_SEED_METADATA_KEY,
638 bloq_ir::MetadataValue::U64(seed),
639 );
640 Ok(artifacts)
641}
642
643fn random_proxy_pins(graph: &BlockGraph, seed: u64) -> Result<Vec<bool>, CompileError> {
644 reject_structural_proxy(graph)?;
647 let (_, replacements) = graph.randomly_resolve_selectives(seed)?;
648 let pins = ordered_blocks(graph)
649 .into_iter()
650 .filter_map(|block| {
651 let BlockKind::Selective(kind) = block.kind() else {
652 return None;
653 };
654 let resolved = replacements
655 .get(block)
656 .expect("random resolution replaces every selective block");
657 let chosen = match resolved.kind() {
658 BlockKind::Y => bloq_graph::PauliBasis::Y,
659 BlockKind::Measurement(basis) => basis.into(),
660 _ => unreachable!("a resolved selective is a Y or measurement block"),
661 };
662 Some(chosen == kind.pauli_if_true())
663 })
664 .collect::<Vec<_>>();
665 Ok(pins)
666}
667
668pub fn compile_detslice_proxy(
679 config: CompileConfig,
680 graph: &BlockGraph,
681 pins: &[bool],
682) -> Result<CompileArtifacts, CompileError> {
683 compile_proxy(config, graph, pins, CliffordProxyPurpose::Detslice)
684}
685
686fn flat_proxy_graph(
687 config: CompileConfig,
688 graph: &BlockGraph,
689) -> Result<Option<BlockGraph>, CompileError> {
690 if !graph.has_module_structure() {
691 return Ok(None);
692 }
693 Ok(Some(
694 graph.flatten_with_limits(config.certification_limits())?,
695 ))
696}
697
698fn compile_proxy(
699 config: CompileConfig,
700 graph: &BlockGraph,
701 pins: &[bool],
702 purpose: CliffordProxyPurpose,
703) -> Result<CompileArtifacts, CompileError> {
704 let flat = flat_proxy_graph(config, graph)?;
705 compile_pinned_clifford_proxy(
706 config,
707 flat.as_ref().unwrap_or(graph),
708 pins,
709 purpose,
710 graph.has_module_structure().then_some(graph),
711 )
712}
713
714fn compile_pinned_clifford_proxy(
715 config: CompileConfig,
716 graph: &BlockGraph,
717 pins: &[bool],
718 purpose: CliffordProxyPurpose,
719 program: Option<&BlockGraph>,
720) -> Result<CompileArtifacts, CompileError> {
721 let expected = graph
722 .blocks()
723 .filter(|block| block.kind().is_selective())
724 .count();
725 if pins.len() != expected {
726 return Err(CompileError::CliffordProxyPinCount {
727 expected,
728 actual: pins.len(),
729 });
730 }
731 static CACHE: OnceLock<crate::SharedCompileCache> = OnceLock::new();
732 let mut ctx = CompileContext::with_shared_cache(
736 config,
737 CACHE.get_or_init(crate::SharedCompileCache::new),
738 );
739 let source_summary = program
740 .map(|program| ctx.summarize(program, config.certification_limits()))
741 .transpose()?;
742 ctx.clifford_proxy = Some(CliffordProxyPins {
743 pins: pins.to_vec(),
744 purpose,
745 source_summary,
746 });
747 ctx.compile(graph)
748}
749
750#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
755pub(crate) enum Signature {
756 Block(BlockSignature),
757 MultiplexPort {
758 boundary_basis: Basis,
759 output_qubit: IVec2,
760 },
761 Realignment {
762 top_basis: Basis,
763 },
764 SpatialHadamardPipe(SpatialHadamardKey),
765}
766
767impl CompileContext {
768 #[must_use]
771 pub fn new(config: CompileConfig) -> Self {
772 Self {
773 config,
774 cache: Arc::new(crate::cache::TemplateCacheShard::default()),
775 summaries: Arc::new(crate::cache::ModuleSummaryCache::default()),
776 clifford_proxy: None,
777 progress_observer: None,
778 cancellation: None,
779 #[cfg(test)]
780 flat_signature_builds: std::sync::atomic::AtomicUsize::new(0),
781 }
782 }
783
784 #[must_use]
792 pub fn with_shared_cache(config: CompileConfig, cache: &crate::SharedCompileCache) -> Self {
793 Self {
794 config,
795 cache: cache.shard(config),
796 summaries: cache.summary_cache(),
797 clifford_proxy: None,
798 progress_observer: None,
799 cancellation: None,
800 #[cfg(test)]
801 flat_signature_builds: std::sync::atomic::AtomicUsize::new(0),
802 }
803 }
804
805 #[must_use]
811 pub fn config(&self) -> CompileConfig {
812 self.config
813 }
814
815 #[must_use]
820 pub fn with_progress_observer(
821 mut self,
822 observer: impl Fn(CompileStage) + Send + Sync + 'static,
823 ) -> Self {
824 self.progress_observer = Some(ProgressObserver(Box::new(observer)));
825 self
826 }
827
828 #[must_use]
835 pub fn with_cancellation(mut self, token: bloq_graph::CancellationToken) -> Self {
836 self.cancellation = Some(token);
837 self
838 }
839
840 fn check_cancellation(&self) -> Result<(), CompileError> {
841 if let Some(token) = &self.cancellation {
842 token.check()?;
843 }
844 Ok(())
845 }
846
847 fn cancellable<T>(
848 &self,
849 f: impl FnOnce() -> Result<T, CompileError>,
850 ) -> Result<T, CompileError> {
851 match &self.cancellation {
852 Some(token) => token.run(f),
853 None => f(),
854 }
855 }
856
857 pub(crate) fn report_progress(&self, stage: CompileStage) -> Result<(), CompileError> {
858 self.check_cancellation()?;
859 if let Some(observer) = &self.progress_observer {
860 (observer.0)(stage);
861 }
862 self.check_cancellation()
863 }
864
865 pub fn summarize(
872 &self,
873 program: &BlockGraph,
874 limits: ModuleCertificationLimits,
875 ) -> Result<Arc<ModuleSummary>, ModuleCertificationError> {
876 self.summaries.get_or_summarize(program, limits, None)
877 }
878
879 pub fn summarize_with_jobs(
886 &self,
887 program: &BlockGraph,
888 limits: ModuleCertificationLimits,
889 jobs: NonZeroUsize,
890 ) -> Result<Arc<ModuleSummary>, ModuleCertificationError> {
891 self.summaries.get_or_summarize(program, limits, Some(jobs))
892 }
893
894 pub fn compile_object(&self, program: &BlockGraph) -> Result<CompiledObject, CompileError> {
903 let object = self.compile_object_with_jobs(program, default_module_jobs())?;
904 self.report_progress(CompileStage::Complete)?;
905 Ok(object)
906 }
907
908 fn compile_object_with_jobs(
909 &self,
910 program: &BlockGraph,
911 jobs: NonZeroUsize,
912 ) -> Result<CompiledObject, CompileError> {
913 self.cancellable(|| {
914 self.report_progress(CompileStage::Validation)?;
915 let hierarchical = program.has_module_structure();
916 if hierarchical {
917 program.validate_with_limits(self.config.certification_limits())?;
918 } else {
919 program.validate_resource_limits(self.config.certification_limits())?;
920 }
921 let key = (hierarchical, program.to_blog_text());
922 let artifacts = if let Some(cached) = self.cache.module_object(&key) {
923 self.report_progress(CompileStage::CacheReuse)?;
924 cached
925 } else {
926 self.report_progress(CompileStage::Certification)?;
927 let artifacts = if hierarchical {
928 registry::compile_hierarchy(self, program, jobs)?
929 } else {
930 registry::compile_graph(self, program)?
931 };
932 self.check_cancellation()?;
933 self.cache.insert_module_object(key, Arc::new(artifacts))
934 };
935 Ok(CompiledObject {
936 config: self.config,
937 artifacts,
938 })
939 })
940 }
941
942 fn prepare_module_definitions(
943 &self,
944 program: &BlockGraph,
945 jobs: NonZeroUsize,
946 ) -> Result<
947 (
948 bloq_graph::LinkedModuleDefinition,
949 Vec<Arc<PreparedDefinitionObject>>,
950 ),
951 CompileError,
952 > {
953 let (definition_keys, seam_faces) = (
954 program.module_definition_cache_keys(),
955 program.module_public_seam_faces(),
956 );
957 let mut linked = bloq_graph::flatten_module_definition(program, program.root(), "")
958 .map_err(|source| ModuleCertificationError::Graph {
959 module: program.root().name.clone(),
960 source,
961 })?;
962 linked.graph = linked.graph.fix_shadowed_faces();
963 let definitions = self.compile_module_definition_objects(
964 program,
965 &linked,
966 &definition_keys,
967 &seam_faces,
968 jobs,
969 )?;
970 Ok((linked, definitions))
971 }
972
973 pub fn link_object(&self, object: &CompiledObject) -> Result<CompileArtifacts, CompileError> {
980 self.check_cancellation()?;
981 if object.config != self.config {
982 return Err(CompileError::CompiledObjectTargetMismatch {
983 object: Box::new(object.config),
984 linker: Box::new(self.config),
985 });
986 }
987 let started_at = Instant::now();
988 let mut artifacts = object.artifacts.as_ref().clone();
989 artifacts.compile_duration = started_at.elapsed();
990 self.check_cancellation()?;
991 Ok(artifacts)
992 }
993
994 pub fn compile_and_validate(
1014 &self,
1015 graph: &BlockGraph,
1016 ) -> Result<CompileArtifacts, CompileError> {
1017 let artifacts = self.compile(graph)?;
1018 let options = bloq_ir::lowering::InstantiationOptions::default()
1019 .with_boolean_limits(self.config.certification_limits().boolean_limits());
1020 artifacts
1021 .bloq
1022 .validate_with_options(&options)
1023 .map_err(CompileError::from_verification_audit)?;
1024 Ok(artifacts)
1025 }
1026
1027 fn preflight_proxy(&self, graph: &BlockGraph) -> Result<ProxyPreflight, CompileError> {
1029 graph.validate_resource_limits(self.config.certification_limits())?;
1030 let proxy = self
1031 .clifford_proxy
1032 .as_ref()
1033 .expect("Clifford proxy context");
1034 let module_summary = proxy.source_summary.as_deref();
1035 let graph = graph.canonical_true_branch_view()?;
1036 graph.validate_source()?;
1037 reject_structural_proxy(&graph)?;
1038 let linked_offset = module_summary
1039 .map(|summary| module_normalization_offset(&graph, summary))
1040 .transpose()?
1041 .unwrap_or(IVec3::ZERO);
1042 let graph = graph.with_zero_min_z()?.fix_shadowed_faces();
1043 let (source_graph, mut stabilizers) = if let Some(summary) = module_summary {
1044 let zx = bloq_graph::ZXGraph::from_block_graph_for_analysis(&graph)
1045 .map_err(bloq_graph::RuntimeBasisError::from)?;
1046 let stabilizers = summary.materialize_stabilizers(&zx, linked_offset)?;
1047 (graph.with_analyzed_action_graph(&stabilizers)?, stabilizers)
1048 } else {
1049 graph.analyze_actions_with_limits(self.config.certification_limits())?
1050 };
1051 let (graph, spatial_ports) =
1052 expand_spatial_ports(&source_graph, self.config.code_distance(), &stabilizers)?;
1053 if module_summary.is_none() {
1055 stabilizers.validate_measurements_close_before_outputs_with_limits(
1056 self.config.certification_limits(),
1057 )?;
1058 }
1059 let changed = stabilizers.prepare_readouts(
1060 source_graph.action_graph(),
1061 self.config.certification_limits(),
1062 )?;
1063 let graph = if changed {
1064 graph.with_analyzed_action_graph(&stabilizers)?
1065 } else {
1066 graph
1067 };
1068 let stabilizers = self.proxy_stabilizers(&graph, &stabilizers)?;
1069 let signatures = select_link_signatures(
1070 self.config,
1071 &graph,
1072 &spatial_ports,
1073 #[cfg(test)]
1074 &self.flat_signature_builds,
1075 )?;
1076 Ok(ProxyPreflight {
1077 graph,
1078 signatures,
1079 stabilizers,
1080 spatial_ports,
1081 })
1082 }
1083
1084 pub fn compile(&self, graph: &BlockGraph) -> Result<CompileArtifacts, CompileError> {
1095 self.cancellable(|| {
1096 let artifacts = if self.clifford_proxy.is_none() {
1097 if graph.has_module_structure() {
1098 let started_at = Instant::now();
1099 let object = self.compile_object_with_jobs(graph, default_module_jobs())?;
1100 let mut artifacts = self.link_object(&object)?;
1101 artifacts.compile_duration = started_at.elapsed();
1102 artifacts
1103 } else {
1104 registry::compile_graph(self, graph)?
1105 }
1106 } else {
1107 let started_at = Instant::now();
1108 self.report_progress(CompileStage::Validation)?;
1109 let prepared = self.prepare_proxy(graph)?;
1110 self.report_progress(CompileStage::Readouts)?;
1111 let mut artifacts = Self::lower_prepared_proxy(self.config, prepared)?;
1112 artifacts.compile_duration = started_at.elapsed();
1113 artifacts
1114 };
1115 self.report_progress(CompileStage::Complete)?;
1116 Ok(artifacts)
1117 })
1118 }
1119
1120 fn compile_module_definition_objects(
1121 &self,
1122 program: &BlockGraph,
1123 linked_root: &bloq_graph::LinkedModuleDefinition,
1124 definition_keys: &std::collections::HashMap<String, String>,
1125 seam_faces: &std::collections::HashMap<(String, IVec3), Vec<bloq_graph::Direction>>,
1126 jobs: NonZeroUsize,
1127 ) -> Result<Vec<Arc<PreparedDefinitionObject>>, CompileError> {
1128 let orientations_by_name = program.definition_orientations();
1129 let mut names = definition_keys.keys().cloned().collect::<Vec<_>>();
1130 names.sort_unstable();
1131 let mut cached = std::collections::HashMap::new();
1134 let mut missing_names = Vec::new();
1135 for name in &names {
1136 let key = DefinitionObjectCacheKey {
1137 implementation: definition_keys[name].clone(),
1138 orientations: orientations_by_name[name].clone(),
1139 };
1140 if let Some(object) = self.cache.definition_object(&key) {
1141 cached.insert(name.clone(), object);
1142 } else {
1143 missing_names.push(name);
1144 }
1145 }
1146 let compile_one = |name: &String| -> Result<CompiledDefinitionVariants, CompileError> {
1147 self.check_cancellation()?;
1148 let mut linked_child;
1151 let linked = if name == BlockGraph::ENTRY_MODULE {
1152 linked_root
1153 } else {
1154 let definition = program.module(name).expect("validated definition");
1155 linked_child = bloq_graph::flatten_module_definition(program, definition, "")
1156 .map_err(|source| ModuleCertificationError::Graph {
1157 module: program.root().name.clone(),
1158 source,
1159 })?;
1160 linked_child.graph = linked_child.graph.fix_shadowed_faces();
1161 &linked_child
1162 };
1163 let mut variants = crate::FxMap::default();
1164 for &orientation in &orientations_by_name[name] {
1165 self.check_cancellation()?;
1166 let oriented_graph = linked.graph.with_orientation_lenient(orientation)?;
1167 let oriented_sites = linked
1168 .sites
1169 .iter()
1170 .map(|(&position, site)| {
1171 let mut site = site.clone();
1172 site.orientation = orientation.then_orientation(site.orientation);
1173 orientation
1174 .try_rotate_position(position)
1175 .map(|position| (position, site))
1176 })
1177 .collect::<Result<std::collections::HashMap<_, _>, _>>()?;
1178 if name != BlockGraph::ENTRY_MODULE {
1181 registry::certify_definition(
1182 name,
1183 &oriented_graph,
1184 &oriented_sites,
1185 self.config.certification_limits(),
1186 )?;
1187 }
1188 let graph = oriented_graph.canonical_true_branch_view()?;
1189 let graph = graph.fix_shadowed_faces();
1190 let (graph, spatial_ports) = crate::spatial_port::expand_spatial_port_topology(
1191 &graph,
1192 self.config.code_distance(),
1193 )?;
1194 let local_positions = oriented_sites
1195 .iter()
1196 .filter(|(_, site)| site.definition == *name && site.instance_path.is_empty())
1197 .map(|(&position, _)| position)
1198 .collect::<crate::FxSet<_>>();
1199 let signatures = relocatable_block_signatures(
1200 &graph,
1201 &local_positions,
1202 self.config.code_distance(),
1203 )?;
1204 let mut inventories = vec![self.compile_definition_variants(
1205 &graph,
1206 &signatures,
1207 &oriented_sites,
1208 seam_faces,
1209 &spatial_ports,
1210 )?];
1211 let regions = graph.branch_regions()?;
1212 if !regions.is_empty() {
1213 let projected = graph.project_branches_deferred(
1214 regions.iter().map(|region| (region.target, false)),
1215 )?;
1216 let signatures = relocatable_block_signatures(
1217 &projected,
1218 &local_positions,
1219 self.config.code_distance(),
1220 )?;
1221 inventories.push(self.compile_definition_variants(
1222 &projected,
1223 &signatures,
1224 &oriented_sites,
1225 seam_faces,
1226 &spatial_ports,
1227 )?);
1228 }
1229
1230 for inventory in inventories {
1231 for (position, entries) in inventory {
1232 let site = &oriented_sites[&position];
1233 debug_assert_eq!(&site.definition, name);
1234 debug_assert!(site.instance_path.is_empty());
1235 for (signature, info) in entries {
1236 let key = DefinitionVariantKey {
1237 local_position: site.local_position,
1238 signature,
1239 graph_connectivity: info.graph_connectivity,
1240 };
1241 if let Some(previous) = variants.insert(key, info) {
1242 debug_assert_eq!(previous, info);
1243 }
1244 }
1245 }
1246 }
1247 }
1248 Ok(variants)
1249 };
1250
1251 let results = bloq_graph::map_jobs(&missing_names, jobs, |&name| compile_one(name));
1252 let parts_by_name = missing_names
1253 .into_iter()
1254 .zip(results)
1255 .map(|(name, result)| result.map(|parts| (name.clone(), parts)))
1256 .collect::<Result<_, _>>()?;
1257
1258 Ok(build_definition_objects(
1259 &self.cache,
1260 &names,
1261 parts_by_name,
1262 cached,
1263 definition_keys,
1264 &orientations_by_name,
1265 program,
1266 ))
1267 }
1268
1269 fn prepare_proxy(&self, graph: &BlockGraph) -> Result<PreparedProxy, CompileError> {
1270 let ProxyPreflight {
1271 graph,
1272 signatures,
1273 stabilizers,
1274 spatial_ports,
1275 } = self.preflight_proxy(graph)?;
1276 self.report_progress(CompileStage::Templates)?;
1277 let mut compiled = self.compile_blocks(&graph, &ordered_blocks(&graph), &signatures)?;
1278 for expansion in spatial_ports.values() {
1279 let derived = compiled
1280 .get_mut(&expansion.source)
1281 .expect("derived cube was compiled");
1282 derived.graph_connectivity = derived
1283 .graph_connectivity
1284 .with_pipe(expansion.cube_pipe_dir());
1285 }
1286 let spatial_port_templates = self.compile_spatial_port_templates(&spatial_ports)?;
1287 let plan = crate::lower::LinkedPlanInput::from_module_graph(
1288 &graph,
1289 &spatial_ports,
1290 compiled.keys().copied(),
1291 );
1292 let temporal_templates = self.compile_temporal_template_plans(plan.temporal_templates())?;
1293 let wall_templates = self.compile_spatial_template_walls(&graph, plan.spatial_walls())?;
1294 let needs_padding =
1295 !spatial_ports.is_empty() || graph.pipes().any(|pipe| !pipe.dir().is_spatial());
1296 let padding_templates = self
1297 .cache
1298 .padding
1299 .prepare(needs_padding, self.config.code_distance())?;
1300 let template_pool = self.cache.pool_snapshot();
1301 Ok(PreparedProxy {
1302 graph,
1303 stabilizers,
1304 spatial_ports,
1305 compiled,
1306 spatial_port_templates,
1307 plan,
1308 temporal_templates,
1309 wall_templates,
1310 padding_templates,
1311 template_pool,
1312 })
1313 }
1314
1315 fn lower_prepared_proxy(
1316 config: CompileConfig,
1317 prepared: PreparedProxy,
1318 ) -> Result<CompileArtifacts, CompileError> {
1319 let PreparedProxy {
1320 graph,
1321 stabilizers,
1322 spatial_ports,
1323 compiled,
1324 spatial_port_templates,
1325 plan,
1326 temporal_templates,
1327 wall_templates,
1328 padding_templates,
1329 template_pool,
1330 } = prepared;
1331 let plan = crate::lower::LowerPlan::from_linked_input(&plan);
1332 let mut bloq = lower_clifford_proxy(
1333 PhysicalInput {
1334 graph: &graph,
1335 plan: &plan,
1336 compiled: &compiled,
1337 spatial_port_templates: &spatial_port_templates,
1338 spatial_ports: &spatial_ports,
1339 temporal_templates: &temporal_templates,
1340 wall_templates: &wall_templates,
1341 template_pool: &template_pool,
1342 layout: BlockLayout::new(config.code_distance()),
1343 },
1344 &stabilizers,
1345 )?;
1346 bloq.insert_metadata(CODE_DISTANCE_METADATA_KEY, config.code_distance());
1347 bloq.insert_metadata(CONVENTION_METADATA_KEY, "fixed-bulk".to_owned());
1348 crate::padding::record_edge_padding(
1351 &padding_templates,
1352 &mut bloq,
1353 &graph,
1354 config.code_distance(),
1355 )?;
1356 Ok(CompileArtifacts {
1357 bloq,
1358 compile_duration: Duration::ZERO,
1359 warnings: crate::config::spatial_hadamard_distance_warning(&graph)
1360 .into_iter()
1361 .collect(),
1362 })
1363 }
1364
1365 fn compile_definition_variants(
1366 &self,
1367 graph: &BlockGraph,
1368 signatures: &BlockSignatureMap,
1369 module_sites: &std::collections::HashMap<IVec3, bloq_graph::MaterializedModuleSite>,
1370 seam_faces: &std::collections::HashMap<(String, IVec3), Vec<bloq_graph::Direction>>,
1371 spatial_ports: &SpatialPortExpansionMap,
1372 ) -> Result<CompiledDefinitionVariantMap, CompileError> {
1373 let mut inventory = CompiledDefinitionVariantMap::default();
1374 for (&position, &signature) in signatures {
1375 let block = graph
1376 .get_block(position)
1377 .expect("compiled site belongs to the prepared graph");
1378 let mut signature = signature;
1379 let mut exact_graph_connectivity = BlockSignature::graph_connectivity(block, graph);
1380 if let Some(expansion) = spatial_ports.get(&position) {
1381 exact_graph_connectivity =
1382 exact_graph_connectivity.with_pipe(expansion.cube_pipe_dir());
1383 signature.connectivity =
1384 signature.connectivity.with_pipe(expansion.cube_pipe_dir());
1385 }
1386 let site = module_sites
1387 .get(&position)
1388 .expect("module materialization records every compiled block");
1389 let mutable_faces = seam_faces
1390 .get(&(site.definition.clone(), site.local_position))
1391 .map(|directions| {
1392 directions
1393 .iter()
1394 .map(|&direction| site.orientation.rotate_direction(direction))
1395 .collect::<Vec<_>>()
1396 })
1397 .unwrap_or_default();
1398 let mut connectivity = vec![(signature.connectivity, exact_graph_connectivity)];
1399 for direction in &mutable_faces {
1400 let direction = *direction;
1401 let mut next = Vec::with_capacity(connectivity.len() * 2);
1402 for (template, graph) in connectivity {
1403 next.push((
1404 if direction.is_spatial() {
1405 template.with_plain_pipe(direction)
1406 } else {
1407 template
1408 },
1409 graph.with_plain_pipe(direction),
1410 ));
1411 next.push((
1412 if direction.is_spatial() {
1413 template.with_hadamard(direction)
1414 } else {
1415 template
1416 },
1417 graph.with_hadamard(direction),
1418 ));
1419 }
1420 connectivity = next;
1421 }
1422
1423 let mut candidates = crate::FxSet::default();
1424 for (template_connectivity, graph_connectivity) in connectivity {
1425 let schedules: &[crate::signature::LayerSchedule] =
1426 if matches!(signature.kind, BlockKind::Cube(_)) {
1427 match template_connectivity.hadamard_wall_axes() {
1428 (true, true) => continue,
1429 (true, false) => &[crate::signature::LayerSchedule::Extended],
1430 (false, true) => &[crate::signature::LayerSchedule::ExtendedY],
1431 (false, false) => &[
1432 crate::signature::LayerSchedule::Compact,
1433 crate::signature::LayerSchedule::Padded,
1434 crate::signature::LayerSchedule::Extended,
1435 crate::signature::LayerSchedule::ExtendedY,
1436 ],
1437 }
1438 } else {
1439 &[]
1440 };
1441 if schedules.is_empty() {
1442 let mut candidate = signature;
1443 candidate.connectivity = template_connectivity;
1444 if candidate.kind == BlockKind::T {
1445 for side in [
1446 bloq_graph::Direction::YMINUS,
1447 bloq_graph::Direction::XPLUS,
1448 bloq_graph::Direction::YPLUS,
1449 bloq_graph::Direction::XMINUS,
1450 ] {
1451 candidate.surgery_side = Some(side);
1452 candidates.insert((candidate, graph_connectivity));
1453 }
1454 } else {
1455 candidates.insert((candidate, graph_connectivity));
1456 }
1457 } else {
1458 for &schedule in schedules {
1459 let mut candidate = signature;
1460 candidate.connectivity = template_connectivity;
1461 candidate.layer_schedule = Some(schedule);
1462 candidates.insert((candidate, graph_connectivity));
1463 }
1464 }
1465 }
1466 if matches!(
1467 signature.kind,
1468 BlockKind::Y
1469 | BlockKind::Measurement(_)
1470 | BlockKind::Port
1471 | BlockKind::Selective(_)
1472 | BlockKind::T
1473 ) && mutable_faces
1474 .iter()
1475 .any(|direction| !direction.is_spatial())
1476 {
1477 candidates = candidates
1478 .drain()
1479 .flat_map(|(candidate, graph_connectivity)| {
1480 [Basis::X, Basis::Z].map(move |basis| {
1481 (
1482 BlockSignature {
1483 boundary_basis: Some(basis),
1484 ..candidate
1485 },
1486 graph_connectivity,
1487 )
1488 })
1489 })
1490 .collect();
1491 }
1492 candidates.insert((signature, exact_graph_connectivity));
1493 let mut variants = Vec::with_capacity(candidates.len());
1494 for (candidate, graph_connectivity) in candidates {
1495 let valid = validate_fixed_bulk(candidate).is_ok();
1496 if !valid && candidate != signature {
1497 continue;
1498 }
1499 let template = self.compile_block_signature(candidate)?;
1500 variants.push((
1501 candidate,
1502 CompiledTemplateInfo {
1503 graph_connectivity,
1504 template,
1505 },
1506 ));
1507 }
1508 inventory.insert(position, variants);
1509 }
1510 Ok(inventory)
1511 }
1512
1513 fn compile_blocks(
1514 &self,
1515 graph: &BlockGraph,
1516 blocks: &[&Block],
1517 signatures: &BlockSignatureMap,
1518 ) -> Result<CompiledTemplateMap, CompileError> {
1519 let mut compiled = crate::FxMap::with_capacity_and_hasher(blocks.len(), Default::default());
1520 let mut proxy_pins = self
1521 .clifford_proxy
1522 .as_ref()
1523 .into_iter()
1524 .flat_map(|proxy| proxy.pins.iter().copied());
1525
1526 for &block in blocks {
1527 let signature = *signatures
1528 .get(&block.pos())
1529 .expect("signature map includes every ordered block");
1530 let template = if self.clifford_proxy.is_some() {
1534 let pin = signature.kind.is_selective().then(|| {
1535 proxy_pins
1536 .next()
1537 .expect("proxy pin count was validated before compilation")
1538 });
1539 self.proxy_template_ref(signature, pin)?
1540 } else {
1541 self.compile_block_signature(signature)?
1542 };
1543 compiled.insert(
1544 block.pos(),
1545 CompiledTemplateInfo {
1546 graph_connectivity: BlockSignature::graph_connectivity(block, graph),
1547 template,
1548 },
1549 );
1550 }
1551
1552 Ok(compiled)
1553 }
1554
1555 fn compile_spatial_port_templates(
1556 &self,
1557 spatial_ports: &SpatialPortExpansionMap,
1558 ) -> Result<CompiledTemplateMap, CompileError> {
1559 let mut compiled = CompiledTemplateMap::default();
1560 for expansion in spatial_ports.values().copied() {
1561 self.check_cancellation()?;
1562 let connectivity = Connectivity::ISOLATED.with_pipe(expansion.port_pipe_dir());
1563 let template = if expansion.role == PortRole::Multiplex {
1564 let output_qubit = expansion
1565 .output_qubit
1566 .expect("Multiplex expansions allocate an output qubit");
1567 TemplateRef::Fixed(self.cache.get_or_compile_fixed(
1568 Signature::MultiplexPort {
1569 boundary_basis: expansion.boundary_basis(),
1570 output_qubit,
1571 },
1572 || {
1573 compile_multiplex_port(
1574 expansion.boundary_basis(),
1575 self.config.code_distance(),
1576 output_qubit,
1577 )
1578 },
1579 )?)
1580 } else {
1581 self.compile_block_signature(BlockSignature {
1582 kind: BlockKind::Port,
1583 rounds: None,
1584 connectivity,
1585 boundary_basis: Some(expansion.boundary_basis()),
1586 layer_schedule: None,
1587 surgery_side: None,
1588 })?
1589 };
1590 compiled.insert(
1591 expansion.source,
1592 CompiledTemplateInfo {
1593 graph_connectivity: connectivity,
1594 template,
1595 },
1596 );
1597 }
1598 Ok(compiled)
1599 }
1600
1601 fn compile_block_signature(
1602 &self,
1603 signature: BlockSignature,
1604 ) -> Result<TemplateRef, CompileError> {
1605 self.check_cancellation()?;
1606 let template = self.cache.get_or_compile(Signature::Block(signature), || {
1607 compile_signature(
1608 signature,
1609 self.config.code_distance(),
1610 self.config.prepare_t_with_mpps(),
1611 )
1612 })?;
1613 self.check_cancellation()?;
1614 Ok(template)
1615 }
1616
1617 fn proxy_template_ref(
1622 &self,
1623 signature: BlockSignature,
1624 pin: Option<bool>,
1625 ) -> Result<TemplateRef, CompileError> {
1626 let proxy = self
1627 .clifford_proxy
1628 .as_ref()
1629 .expect("caller checked the proxy is active");
1630 if signature.kind == BlockKind::T && proxy.purpose == CliffordProxyPurpose::Distance {
1631 return self.compile_block_signature(BlockSignature {
1632 kind: BlockKind::Port,
1633 rounds: None,
1634 connectivity: Connectivity::ISOLATED.with_pipe(bloq_graph::Direction::ZPLUS),
1635 boundary_basis: signature.boundary_basis,
1636 layer_schedule: None,
1637 surgery_side: None,
1638 });
1639 }
1640 let template = self.compile_block_signature(signature)?;
1641 match template {
1642 TemplateRef::Fixed(_) => Ok(template),
1643 TemplateRef::Selective {
1644 when_true,
1645 when_false,
1646 } => Ok(TemplateRef::Fixed(
1647 if pin.expect("selective has a proxy pin") {
1648 when_true
1649 } else {
1650 when_false
1651 },
1652 )),
1653 t @ TemplateRef::T { .. } if proxy.purpose == CliffordProxyPurpose::Detslice => Ok(t),
1654 TemplateRef::T { .. } => unreachable!("distance proxies return above"),
1655 }
1656 }
1657
1658 fn proxy_stabilizers(
1661 &self,
1662 graph: &BlockGraph,
1663 stabilizers: &StabilizerGenerators,
1664 ) -> Result<StabilizerGenerators, CompileError> {
1665 let proxy = self
1666 .clifford_proxy
1667 .as_ref()
1668 .expect("caller checked the proxy is active");
1669 let basis = RuntimeStabilizerBasis::from_generators(stabilizers).with_t_nodes_as_ports();
1670 let mut pins = proxy.pins.iter().copied();
1671 let mut fills = Vec::new();
1672 for block in ordered_blocks(graph) {
1673 let BlockKind::Selective(kind) = block.kind() else {
1674 continue;
1675 };
1676 let pin = pins
1677 .next()
1678 .expect("compile_clifford_proxy validated the selective pin count");
1679 let chosen = if pin {
1680 kind.pauli_if_true()
1681 } else {
1682 kind.pauli_if_false()
1683 };
1684 fills.push((block.pos(), chosen));
1685 }
1686 debug_assert!(pins.next().is_none());
1687
1688 Ok(basis.apply_selective_fills(&fills)?.into_generators())
1691 }
1692
1693 fn compile_temporal_template_plans(
1694 &self,
1695 plans: impl IntoIterator<Item = crate::lower::TemplatePlan>,
1696 ) -> Result<crate::FxMap<crate::lower::TemplatePlan, LoweringTemplateId>, CompileError> {
1697 let mut templates = crate::FxMap::default();
1698 for template_plan in plans {
1699 let crate::lower::TemplatePlan { top_basis, .. } = template_plan;
1700 let template = self.temporal_template(top_basis)?;
1701 templates.insert(template_plan, template);
1702 }
1703 Ok(templates)
1704 }
1705
1706 fn compile_spatial_template_walls(
1707 &self,
1708 graph: &BlockGraph,
1709 walls: impl IntoIterator<Item = crate::lower::SpatialPipeRef>,
1710 ) -> Result<crate::FxMap<crate::lower::SpatialPipeRef, LoweringTemplateId>, CompileError> {
1711 let mut templates = crate::FxMap::default();
1712 for wall in walls {
1713 self.check_cancellation()?;
1714 let distance = self.config.code_distance();
1715 let key = spatial_hadamard_key(graph, wall, distance)?;
1716 let template = self
1717 .cache
1718 .get_or_compile_fixed(Signature::SpatialHadamardPipe(key), || {
1719 compile_spatial_hadamard(distance, key)
1720 })?;
1721 templates.insert(wall, template);
1722 }
1723 Ok(templates)
1724 }
1725
1726 pub(crate) fn temporal_template(
1727 &self,
1728 top_basis: Basis,
1729 ) -> Result<LoweringTemplateId, CompileError> {
1730 self.check_cancellation()?;
1731 let distance = self.config.code_distance();
1732 let template = self
1733 .cache
1734 .get_or_compile_fixed(Signature::Realignment { top_basis }, || {
1735 compile_realignment(distance, top_basis)
1736 })?;
1737 self.check_cancellation()?;
1738 Ok(template)
1739 }
1740}
1741
1742fn spatial_hadamard_key(
1749 graph: &BlockGraph,
1750 wall: crate::lower::SpatialPipeRef,
1751 distance: u32,
1752) -> Result<SpatialHadamardKey, CompileError> {
1753 let endpoint = |pos: IVec3| -> Result<(&Block, bloq_graph::CubeKind), CompileError> {
1754 let block = graph
1755 .get_endpoint_block(pos)
1756 .ok_or(CompileError::SpatialHadamardUnsupportedEndpoint { pos })?;
1757 match block.kind() {
1758 BlockKind::Cube(kind) => Ok((block, kind)),
1759 _ => Err(CompileError::SpatialHadamardUnsupportedEndpoint { pos }),
1760 }
1761 };
1762 let (minus_block, minus_kind) = endpoint(wall.src)?;
1763 let (plus_block, plus_kind) = endpoint(wall.dst)?;
1764 let axis = wall.axis();
1765 let boundary_basis = match axis {
1766 bloq_graph::UDirection::X => minus_kind.y(),
1767 bloq_graph::UDirection::Y => minus_kind.x(),
1768 bloq_graph::UDirection::Z => unreachable!("spatial Hadamard walls are not temporal"),
1769 };
1770 let make_side = |block: &Block, kind: bloq_graph::CubeKind| {
1771 let connectivity = BlockSignature::template_connectivity(block, graph);
1772 WallSide {
1773 temporal_basis: kind.z(),
1774 connectivity,
1775 }
1776 };
1777 Ok(SpatialHadamardKey {
1778 axis,
1779 rounds: crate::signature::cube_rounds(minus_block, distance)?
1784 .max(crate::signature::cube_rounds(plus_block, distance)?),
1785 boundary_basis,
1786 minus: make_side(minus_block, minus_kind),
1787 plus: make_side(plus_block, plus_kind),
1788 })
1789}
1790
1791fn compile_signature(
1800 signature: BlockSignature,
1801 distance: u32,
1802 prepare_t_with_mpps: bool,
1803) -> Result<crate::cache::CompiledTemplates, CompileError> {
1804 use crate::cache::CompiledTemplates;
1805
1806 if let BlockKind::Selective(kind) = signature.kind {
1807 let boundary_basis = signature
1808 .boundary_basis
1809 .expect("validate ensures a selective block has a boundary basis");
1810 let SelectiveTemplates {
1811 when_true,
1812 when_false,
1813 } = compile_selective(signature.connectivity, distance, boundary_basis, kind)?;
1814 return Ok(CompiledTemplates::Selective {
1815 when_true,
1816 when_false,
1817 });
1818 }
1819 if signature.kind == BlockKind::T {
1820 let boundary_basis = signature
1821 .boundary_basis
1822 .expect("validate ensures a T block has a boundary basis");
1823 let side = signature
1824 .surgery_side
1825 .expect("block_signatures picks a surgery side for every T block");
1826 let layout = crate::block::fixed_bulk::t::SurgeryLayout::new(
1829 side,
1830 boundary_basis.flip(),
1831 boundary_basis,
1832 distance,
1833 );
1834 if prepare_t_with_mpps {
1835 return Ok(CompiledTemplates::Fixed(std::sync::Arc::new(
1836 crate::block::fixed_bulk::t::prepare_t_with_mpps(&layout)?,
1837 )));
1838 }
1839 let cultivation = crate::block::fixed_bulk::t::steane::cultivation_template(&layout)?;
1840 let escape = crate::block::fixed_bulk::t::escape::escape_template(&layout)?;
1841 return Ok(CompiledTemplates::T {
1842 cultivation: std::sync::Arc::new(cultivation),
1843 escape: std::sync::Arc::new(escape),
1844 });
1845 }
1846 let template = compile_fixed_bulk(signature, distance)?;
1847 Ok(CompiledTemplates::Fixed(template))
1848}
1849
1850fn ordered_blocks(graph: &BlockGraph) -> Vec<&Block> {
1851 let mut blocks: Vec<_> = graph.blocks().collect();
1852 blocks.sort_unstable_by_key(|block| {
1853 let pos = block.pos();
1854 (pos.z, pos.x, pos.y)
1855 });
1856 blocks
1857}
1858
1859fn reject_structural_proxy(graph: &BlockGraph) -> Result<(), CompileError> {
1862 if !graph.branch_regions()?.is_empty() {
1863 return Err(CompileError::StructuralBranchCliffordProxyUnsupported);
1864 }
1865 Ok(())
1866}
1867
1868#[cfg(test)]
1869mod tests {
1870 use bloq_ir::{LevelPath, MemoryRoundTarget};
1871
1872 use super::*;
1873 use crate::signature::LayerSchedule;
1874 use bloq_graph::{
1875 Action, BranchArm, CubeKind, Direction, GalleryItem, MeasureTarget, PatchRotationKind,
1876 Pipe, WalkingBoundaryKind, WalkingKind,
1877 };
1878 use glam::ivec3;
1879 use rstest::rstest;
1880 #[test]
1885 fn compilation_entry_points_honor_resource_limits() {
1886 let graph = GalleryItem::CNOT
1887 .build()
1888 .flatten()
1889 .expect("gallery graph expands");
1890 let module = graph.clone().with_inferred_interface().unwrap();
1891 let config =
1892 CompileConfig::default().with_certification_limits(ModuleCertificationLimits {
1893 max_occupied_cells: 0,
1894 ..ModuleCertificationLimits::DEFAULT
1895 });
1896 let context = CompileContext::new(config);
1897 for error in [
1898 context.compile(&graph).unwrap_err(),
1899 context.compile(&module).unwrap_err(),
1900 compile_clifford_proxy(config, &graph, &[]).unwrap_err(),
1901 ] {
1902 assert!(error.is_resource_limited(), "{error}");
1903 assert!(
1904 error.to_string().contains("occupied footprint cells"),
1905 "{error}"
1906 );
1907 }
1908
1909 let config =
1910 CompileConfig::default().with_certification_limits(ModuleCertificationLimits {
1911 max_boolean_nodes: 0,
1912 ..ModuleCertificationLimits::DEFAULT
1913 });
1914 let error = CompileContext::new(config)
1915 .compile(&two_structural_branch_graph())
1916 .unwrap_err();
1917 assert!(error.to_string().contains("Boolean nodes"), "{error}");
1918 assert!(error.is_resource_limited());
1919
1920 let limits = ModuleCertificationLimits {
1921 max_boolean_steps: 0,
1922 ..ModuleCertificationLimits::DEFAULT
1923 };
1924 let error = CompileContext::new(CompileConfig::default().with_certification_limits(limits))
1925 .compile(&graph)
1926 .unwrap_err();
1927 assert!(error.is_resource_limited(), "{error}");
1928 assert_eq!(
1929 error.resource_limit_help(),
1930 Some(ModuleCertificationLimits::RESOURCE_LIMIT_HELP)
1931 );
1932 assert!(
1933 error.to_string().contains("with_certification_limits"),
1934 "{error}"
1935 );
1936 CompileContext::new(CompileConfig::default())
1937 .compile(&graph)
1938 .unwrap();
1939
1940 let exhausted = bloq_utils::boolean::BooleanResourceError {
1941 resource: "Boolean work steps",
1942 observed: 2,
1943 limit: 1,
1944 };
1945 let parse_error = bloq_graph::ParseError::Graph {
1946 source: Box::new(bloq_graph::BlockGraphError::from(exhausted)),
1947 span: None,
1948 };
1949 for (error, certification_help) in [
1950 (CompileError::from(exhausted), true),
1951 (
1952 CompileError::from(bloq_ir::BloqValidationError::from(exhausted)),
1953 false,
1954 ),
1955 (
1956 CompileError::from(bloq_circuit::CircuitError::FlattenResourceLimit {
1957 observed: 2,
1958 limit: 1,
1959 }),
1960 false,
1961 ),
1962 (
1963 CompileError::from(bloq_graph::RuntimeBasisError::from(exhausted)),
1964 true,
1965 ),
1966 (
1967 CompileError::from(bloq_graph::BlockGraphError::Zx(bloq_graph::ZXError::Graph(
1968 Box::new(bloq_graph::BlockGraphError::from(exhausted)),
1969 ))),
1970 true,
1971 ),
1972 (
1973 CompileError::from(bloq_graph::ModuleError::Parse(parse_error.clone())),
1974 true,
1975 ),
1976 (
1977 CompileError::from(bloq_graph::BlockGraphError::Parse(parse_error)),
1978 true,
1979 ),
1980 ] {
1981 assert!(error.is_resource_limited(), "{error:?}");
1982 assert_eq!(
1983 error.resource_limit_help().is_some(),
1984 certification_help,
1985 "{error:?}"
1986 );
1987 }
1988 assert!(!CompileError::InvalidDistance(2).is_resource_limited());
1989 assert!(
1990 CompileError::InvalidDistance(2)
1991 .resource_limit_help()
1992 .is_none()
1993 );
1994 }
1995
1996 #[test]
1997 fn verification_audit_hints_only_for_inherited_boolean_limits() {
1998 use bloq_ir::lowering::NodeTemplateInstanceMergeError;
1999 use bloq_ir::{BloqNodeId, BloqValidationError, TemplateId};
2000
2001 let exhausted = bloq_utils::boolean::BooleanResourceError {
2002 resource: "Boolean work steps",
2003 observed: 2,
2004 limit: 1,
2005 };
2006 for error in [
2007 BloqValidationError::BooleanResource(exhausted),
2008 BloqValidationError::InvalidInstanceMergeStructure {
2009 node: BloqNodeId(3),
2010 source: NodeTemplateInstanceMergeError::BooleanResource(exhausted),
2011 },
2012 BloqValidationError::InvalidTemplateCircuit {
2013 template: TemplateId(4),
2014 source: NodeTemplateInstanceMergeError::BooleanResource(exhausted),
2015 },
2016 ] {
2017 let error = CompileError::from_verification_audit(error);
2018 assert!(
2019 matches!(&error, CompileError::BooleanResource(resource) if *resource == exhausted)
2020 );
2021 assert_eq!(
2022 error.resource_limit_help(),
2023 Some(ModuleCertificationLimits::RESOURCE_LIMIT_HELP)
2024 );
2025 }
2026 for (error, resource_limited) in [
2027 (
2028 BloqValidationError::InvalidInstanceMergeStructure {
2029 node: BloqNodeId(3),
2030 source: NodeTemplateInstanceMergeError::BodyCountOverflow,
2031 },
2032 true,
2033 ),
2034 (
2035 BloqValidationError::InvalidTemplateCircuit {
2036 template: TemplateId(4),
2037 source: NodeTemplateInstanceMergeError::NoiseRepeatExpansion(
2038 bloq_ir::FlattenError::Circuit(
2039 bloq_circuit::CircuitError::FlattenResourceLimit {
2040 observed: 2,
2041 limit: 1,
2042 },
2043 ),
2044 ),
2045 },
2046 true,
2047 ),
2048 (BloqValidationError::CyclicGraph, false),
2049 ] {
2050 let converted = CompileError::from_verification_audit(error.clone());
2051 assert!(matches!(&converted, CompileError::BloqValidation(source) if source == &error));
2052 assert_eq!(converted.is_resource_limited(), resource_limited);
2053 assert!(converted.resource_limit_help().is_none());
2054 }
2055 }
2056
2057 #[test]
2058 fn clifford_proxy_preserves_source_generator_selection_and_order() {
2059 let graph = GalleryItem::CNOT
2060 .build()
2061 .flatten()
2062 .expect("gallery graph expands");
2063 let mut source = graph.stabilizers().unwrap();
2064 source.generators.reverse();
2065 source.generators.pop().expect("CNOT has generators");
2066 let ctx = CompileContext {
2067 clifford_proxy: Some(CliffordProxyPins {
2068 pins: Vec::new(),
2069 purpose: CliffordProxyPurpose::Distance,
2070 source_summary: None,
2071 }),
2072 ..CompileContext::new(CompileConfig::default())
2073 };
2074 let resolved = ctx.proxy_stabilizers(&graph, &source).unwrap();
2075 assert_eq!(resolved.generators, source.generators);
2076 }
2077
2078 fn single_cube_runtime_graph() -> BlockGraph {
2079 let mut graph = BlockGraph::new();
2080 graph.add_block(bloq_graph::Block::new(
2081 ivec3(0, 0, 0),
2082 BlockKind::Cube(CubeKind::ZXZ),
2083 ));
2084 graph
2085 .add_action(Action::Measure {
2086 target: MeasureTarget::Node(ivec3(0, 0, 0)),
2087 name: "skip-derive".to_string(),
2088 })
2089 .expect("add skip-derive measurement action");
2090 graph
2091 }
2092
2093 fn structural_branch_graph() -> BlockGraph {
2094 let prefix = ivec3(0, 0, 0);
2095 let target = ivec3(0, 0, 1);
2096 let reader = ivec3(2, 0, 0);
2097 let mut graph = BlockGraph::new();
2098 for pos in [prefix, reader] {
2099 graph.add_block(Block::new(pos, BlockKind::Cube(CubeKind::ZXZ)));
2100 }
2101 let false_arm = BranchArm::new(
2102 vec![Block::new(target, BlockKind::Measurement(Basis::Z))],
2103 vec![Pipe::new(prefix, Direction::ZPLUS).with_hadamard()],
2104 );
2105 let true_arm = BranchArm::new(
2106 vec![Block::new(target, BlockKind::Cube(CubeKind::XZX))],
2107 vec![Pipe::new(prefix, Direction::ZPLUS).with_hadamard()],
2108 );
2109 let target = graph
2110 .try_add_branch_region("b0", false_arm, true_arm)
2111 .unwrap();
2112 graph
2113 .set_actions(vec![
2114 Action::Measure {
2115 target: MeasureTarget::Node(reader),
2116 name: "m".into(),
2117 },
2118 Action::Branch {
2119 target,
2120 condition: bloq_graph::Expr::Var("m".into()),
2121 },
2122 ])
2123 .unwrap();
2124 graph
2125 }
2126
2127 fn mixed_structural_dynamic_graph() -> BlockGraph {
2128 let mut graph = GalleryItem::T
2129 .build()
2130 .flatten()
2131 .expect("gallery graph expands");
2132 let prefix = ivec3(4, 0, 0);
2133 let target = prefix + IVec3::Z;
2134 graph.add_block(Block::new(prefix, BlockKind::Cube(CubeKind::ZXZ)));
2135 let false_arm = BranchArm::new(
2136 vec![Block::new(target, BlockKind::Measurement(Basis::Z))],
2137 vec![Pipe::new(prefix, Direction::ZPLUS).with_hadamard()],
2138 );
2139 let true_arm = BranchArm::new(
2140 vec![Block::new(target, BlockKind::Cube(CubeKind::XZX))],
2141 vec![Pipe::new(prefix, Direction::ZPLUS).with_hadamard()],
2142 );
2143 let target = graph
2144 .try_add_branch_region("b0", false_arm, true_arm)
2145 .unwrap();
2146 let mut actions = graph.actions();
2147 actions.push(Action::Branch {
2148 target,
2149 condition: bloq_graph::Expr::Var("mzz".into()),
2150 });
2151 graph.set_actions(actions).unwrap();
2152 graph
2153 }
2154
2155 fn two_structural_branch_graph() -> BlockGraph {
2156 let mut graph = GalleryItem::GHZ
2157 .build()
2158 .flatten()
2159 .expect("gallery graph expands");
2160 let mut actions = Vec::new();
2161 for (index, (past, kind)) in [
2162 (ivec3(0, -1, 1), CubeKind::XZZ),
2163 (ivec3(-1, 0, 1), CubeKind::ZXZ),
2164 ]
2165 .into_iter()
2166 .enumerate()
2167 {
2168 let target = past + IVec3::Z;
2169 let reader = ivec3(20 + index as i32, 0, 0);
2170 graph.set_block_kind(past, BlockKind::Cube(kind)).unwrap();
2171 graph.add_block(Block::new(reader - IVec3::Z, BlockKind::Port));
2172 graph.add_block(Block::new(reader, BlockKind::Cube(CubeKind::XZX)));
2173 graph.add_pipe(Pipe::new(reader - IVec3::Z, Direction::ZPLUS));
2174 let arm = |kind| {
2175 BranchArm::new(
2176 vec![Block::new(target, kind)],
2177 vec![Pipe::new(past, Direction::ZPLUS)],
2178 )
2179 };
2180 let target = graph
2181 .try_add_branch_region(
2182 format!("b{index}"),
2183 arm(BlockKind::Measurement(Basis::Z)),
2184 arm(BlockKind::Cube(kind)),
2185 )
2186 .unwrap();
2187 let measurement = format!("m{index}");
2188 actions.push(Action::Measure {
2189 target: MeasureTarget::Node(reader),
2190 name: measurement.clone(),
2191 });
2192 actions.push(Action::Branch {
2193 target,
2194 condition: bloq_graph::Expr::Var(measurement),
2195 });
2196 }
2197 graph.set_actions(actions).unwrap();
2198 graph
2199 }
2200
2201 #[test]
2202 fn audited_structural_branch_retains_compiled_artifacts() {
2203 let graph = structural_branch_graph();
2204 let context = CompileContext::new(CompileConfig::new(3));
2205
2206 let artifacts = context.compile_and_validate(&graph).unwrap();
2207 assert_eq!(compiled_distance(&artifacts.bloq), Some(3));
2208 assert!(artifacts.bloq.quantum_node_count() > 0);
2209 assert!(artifacts.bloq.has_conditional_membership());
2210 }
2211
2212 #[test]
2213 fn repeated_branch_variables_obey_classical_slot_exactness() {
2214 for bit in [false, true] {
2215 for operation in [
2216 bloq_graph::BinaryOp::Xor,
2217 bloq_graph::BinaryOp::And,
2218 bloq_graph::BinaryOp::Or,
2219 ] {
2220 let mut graph = structural_branch_graph();
2221 let mut actions = graph.actions();
2222 let Action::Branch { condition, .. } = actions.last_mut().unwrap() else {
2223 unreachable!()
2224 };
2225 let leaf = if bit {
2226 bloq_graph::Expr::Not(Box::new(condition.clone()))
2227 } else {
2228 condition.clone()
2229 };
2230 *condition =
2231 bloq_graph::Expr::Binary(operation, Box::new(leaf.clone()), Box::new(leaf));
2232 graph.set_actions(actions).unwrap();
2233 let program = compile(&graph, 3).unwrap();
2234 program.validate().unwrap();
2235 let report = bloq_vm::run_bloq(&program, 2, 0).unwrap();
2236 let expected = operation != bloq_graph::BinaryOp::Xor && bit;
2237 assert_eq!(report.branch_selectors, [expected; 2]);
2238 assert_eq!(report.discarded, 0);
2239 assert!(report.all_detectors_constant());
2240 }
2241 }
2242 }
2243
2244 #[test]
2245 fn structural_branch_composes_with_t_and_selective_components() {
2246 use bloq_ir::{BloqNodeKind, RegionNode};
2247
2248 let graph = mixed_structural_dynamic_graph();
2249 let context = CompileContext::new(CompileConfig::new(3));
2250
2251 let program = context.compile_and_validate(&graph).unwrap().bloq;
2252 let regions = program
2253 .nodes()
2254 .filter(|(_, node)| {
2255 matches!(
2256 node.kind,
2257 BloqNodeKind::Region(RegionNode::RepeatUntilSuccess { .. })
2258 )
2259 })
2260 .count();
2261 assert_eq!(regions, 1);
2262 assert!(program.has_conditional_membership());
2263 assert!(program.nodes().any(|(_, node)| matches!(
2264 node.provenance,
2265 bloq_ir::NodeProvenance::BranchSelector { .. }
2266 )));
2267
2268 let report = bloq_vm::run_bloq(&program, 8, 7).unwrap();
2269 assert_eq!(report.discarded, 0);
2270 assert!(report.all_detectors_constant());
2271 assert!(report.max_rank > 1);
2272 assert_eq!(report.frame_pairs.len(), 1);
2273 assert!(report.branch_selectors.contains(&false));
2274 assert!(report.branch_selectors.contains(&true));
2275 }
2276
2277 #[test]
2278 fn compile_and_validate_checks_false_arm_physical_signatures() {
2279 let mut graph = BlockGraph::new();
2280 let prefix = ivec3(0, 0, 0);
2281 let a = ivec3(0, 0, 1);
2282 let b = ivec3(1, 0, 1);
2283 let c = ivec3(1, 1, 1);
2284 let reader = ivec3(4, 0, 0);
2285 graph.add_block(Block::new(prefix, BlockKind::Cube(CubeKind::XZX)));
2286 graph.add_block(Block::new(reader, BlockKind::Cube(CubeKind::ZXZ)));
2287 let target = graph
2288 .try_add_branch_region(
2289 "mixed_wall",
2290 BranchArm::new(
2291 vec![
2292 Block::new(a, BlockKind::Cube(CubeKind::XZX)),
2293 Block::new(b, BlockKind::Cube(CubeKind::XXZ)),
2294 Block::new(c, BlockKind::Cube(CubeKind::ZXX)),
2295 ],
2296 vec![
2297 Pipe::new(prefix, Direction::ZPLUS),
2298 Pipe::new(a, Direction::XPLUS).with_hadamard(),
2299 Pipe::new(b, Direction::YPLUS).with_hadamard(),
2300 ],
2301 ),
2302 BranchArm::new(
2303 vec![Block::new(a, BlockKind::Cube(CubeKind::XZX))],
2304 vec![Pipe::new(prefix, Direction::ZPLUS)],
2305 ),
2306 )
2307 .unwrap();
2308 graph
2309 .set_actions(vec![
2310 Action::Measure {
2311 target: MeasureTarget::Node(reader),
2312 name: "m".into(),
2313 },
2314 Action::Branch {
2315 target,
2316 condition: bloq_graph::Expr::Var("m".into()),
2317 },
2318 ])
2319 .unwrap();
2320 let context = CompileContext::new(CompileConfig::new(3));
2321 assert!(matches!(
2322 context.compile_and_validate(&graph),
2323 Err(CompileError::MixedSpatialHadamardUnsupported { .. })
2324 ));
2325 assert!(matches!(
2326 context.compile(&graph),
2327 Err(CompileError::MixedSpatialHadamardUnsupported { .. })
2328 ));
2329 }
2330
2331 #[test]
2332 fn t_spill_reserves_both_structural_arms() {
2333 let mut graph = BlockGraph::new();
2334 let t = ivec3(0, 0, 1);
2335 let prefix = ivec3(1, -1, 0);
2336 let arm = ivec3(1, -1, 1);
2337 let spill = ivec3(0, -1, 1);
2338 let reader = ivec3(4, 0, 0);
2339 graph.add_block(Block::new(t, BlockKind::T));
2340 graph.add_block(Block::new([0, 0, 2], BlockKind::Port));
2341 graph.add_pipe(Pipe::new(t, Direction::ZPLUS));
2342 for position in [prefix, reader] {
2343 graph.add_block(Block::new(position, BlockKind::Cube(CubeKind::ZXZ)));
2344 }
2345 let target = graph
2346 .try_add_branch_region(
2347 "spill",
2348 BranchArm::new(
2349 vec![
2350 Block::new(arm, BlockKind::Cube(CubeKind::ZXZ)),
2351 Block::new(spill, BlockKind::Cube(CubeKind::ZXZ)),
2352 ],
2353 vec![
2354 Pipe::new(prefix, Direction::ZPLUS),
2355 Pipe::new(arm, Direction::XMINUS),
2356 ],
2357 ),
2358 BranchArm::new(
2359 vec![Block::new(arm, BlockKind::Cube(CubeKind::ZXZ))],
2360 vec![Pipe::new(prefix, Direction::ZPLUS)],
2361 ),
2362 )
2363 .unwrap();
2364 for selected in [false, true] {
2365 let condition = bloq_graph::Expr::Var("m".into());
2366 graph
2367 .set_actions(vec![
2368 Action::Measure {
2369 target: MeasureTarget::Node(reader),
2370 name: "m".into(),
2371 },
2372 Action::Branch {
2373 target,
2374 condition: if selected {
2375 bloq_graph::Expr::Not(Box::new(condition))
2376 } else {
2377 condition
2378 },
2379 },
2380 ])
2381 .unwrap();
2382 let context = CompileContext::new(CompileConfig::new(3));
2383 for module in [false, true] {
2384 let program = if module {
2385 context.compile(&graph.clone().with_inferred_interface().unwrap())
2386 } else {
2387 context.compile_and_validate(&graph)
2388 }
2389 .unwrap()
2390 .bloq;
2391 crate::validate_bloq_qubit_layout_for_source(&program, &graph).unwrap();
2392 let report = bloq_vm::run_bloq(&program, 2, 7).unwrap();
2393 assert_eq!(report.discarded, 0);
2394 assert!(
2395 report
2396 .detectors
2397 .iter()
2398 .all(|detector| detector.per_shot.iter().all(|&bit| !bit))
2399 );
2400 assert_eq!(report.branch_selectors, [selected; 2]);
2401 }
2402 }
2403 for position in [ivec3(1, 0, 1), ivec3(0, 1, 1), ivec3(-1, 0, 1)] {
2404 graph.add_block(Block::new(position, BlockKind::Cube(CubeKind::ZXZ)));
2405 }
2406 let context = CompileContext::new(CompileConfig::new(3));
2407 assert!(matches!(
2408 context.compile_and_validate(&graph),
2409 Err(CompileError::TBlockNeedsFreeNeighbor { .. })
2410 ));
2411 assert!(matches!(
2412 context.compile(&graph.with_inferred_interface().unwrap()),
2413 Err(CompileError::TBlockNeedsFreeNeighbor { .. })
2414 ));
2415 }
2416
2417 #[test]
2418 fn independent_structural_deltas_execute_all_joint_choices() {
2419 let module = two_structural_branch_graph()
2420 .with_inferred_interface()
2421 .unwrap();
2422 let context = CompileContext::new(CompileConfig::new(3));
2423 let program = context.compile(&module).unwrap().bloq;
2424 program.validate().unwrap();
2425
2426 let report = bloq_vm::run_bloq(&program, 64, 0xB12A).unwrap();
2427 assert_eq!(report.discarded, 0);
2428 assert!(report.all_detectors_constant());
2429 let choices = report
2430 .branch_selectors
2431 .as_chunks::<2>()
2432 .0
2433 .iter()
2434 .map(|bits| (bits[0], bits[1]))
2435 .collect::<std::collections::HashSet<_>>();
2436 assert_eq!(choices.len(), 4);
2437 assert_eq!(report.frame_pairs.len(), 2);
2438 }
2439
2440 #[test]
2441 fn clifford_proxy_entrypoints_reject_structural_branch() {
2442 let graph = structural_branch_graph();
2443 let rejected = |result: Result<CompileArtifacts, CompileError>| {
2444 assert!(matches!(
2445 result,
2446 Err(CompileError::StructuralBranchCliffordProxyUnsupported)
2447 ));
2448 };
2449
2450 rejected(compile_clifford_proxy(
2451 CompileConfig::default(),
2452 &graph,
2453 &[],
2454 ));
2455 rejected(compile_random_clifford_proxy(
2456 CompileConfig::default(),
2457 &graph,
2458 7,
2459 ));
2460 rejected(compile_detslice_proxy(
2461 CompileConfig::default(),
2462 &graph,
2463 &[],
2464 ));
2465 }
2466
2467 fn source_timeline(program: &Bloq, pos: IVec3) -> Option<&bloq_ir::QuantumTimeline> {
2468 program.nodes().find_map(|(_, node)| {
2469 node.block_members()
2470 .iter()
2471 .any(|member| member.pos == pos)
2472 .then(|| node.expect_quantum().timeline.as_ref())
2473 .flatten()
2474 })
2475 }
2476
2477 #[test]
2478 fn compilation_rejects_measurement_surface_on_output_port() {
2479 let graph = BlockGraph::from_blog_text(
2480 "BLOG 1.0\n\n\
2481 1: XZX [0,0,1]\n\
2482 2: Port [0,0,2]\n\
2483 3: T [1,0,0]\n\
2484 4: XZX [1,0,1]\n\
2485 5: YX [1,0,2]\n\
2486 [0,0,1] -> +Z\n\
2487 [0,0,1] -> +X\n\
2488 [1,0,0] -> +Z\n\
2489 [1,0,2] -> -Z\n\n\
2490 mzz = measure 1 -> +X\n\
2491 resolve 5 if mzz\n",
2492 )
2493 .unwrap();
2494 let stabilizers = graph.stabilizers().unwrap();
2495 let measurement = stabilizers
2496 .generators
2497 .iter()
2498 .find(|row| row.measurement_name() == Some("mzz"))
2499 .expect("the merge supplies the named measurement");
2500 assert_eq!(
2501 measurement.stabilizer.port_stabilizer.get(&ivec3(0, 0, 2)),
2502 Some(&bloq_graph::Pauli::Z),
2503 );
2504 assert!(matches!(
2505 stabilizers.validate_measurements_close_before_outputs(),
2506 Err(bloq_graph::RuntimeBasisError::Stabilizer(
2507 bloq_graph::StabilizerError::MeasurementSurfaceTouchesOutputPort {
2508 ref name,
2509 port,
2510 }
2511 )) if name == "mzz" && port == ivec3(0, 0, 2)
2512 ));
2513 assert!(matches!(
2514 compile_clifford_proxy(CompileConfig::default(), &graph, &[false]),
2515 Err(CompileError::RuntimeBasis(
2516 bloq_graph::RuntimeBasisError::Stabilizer(
2517 bloq_graph::StabilizerError::MeasurementSurfaceTouchesOutputPort {
2518 ref name,
2519 port,
2520 }
2521 )
2522 )) if name == "mzz" && port == ivec3(0, 0, 2)
2523 ));
2524 let context = CompileContext::new(CompileConfig::default());
2525
2526 for error in [
2527 context.compile_and_validate(&graph).unwrap_err(),
2528 context.compile(&graph).unwrap_err(),
2529 ] {
2530 assert!(matches!(
2531 error,
2532 CompileError::BlockGraph(bloq_graph::BlockGraphError::Stabilizer(
2533 bloq_graph::StabilizerError::UnavailableControlParity { ref mvar, .. }
2534 )) if mvar == "mzz"
2535 ));
2536 }
2537 }
2538
2539 #[test]
2540 fn t_region_observable_ids_fail_before_wrapping() {
2541 let context = CompileContext {
2542 clifford_proxy: Some(CliffordProxyPins {
2543 pins: vec![false],
2544 purpose: CliffordProxyPurpose::Detslice,
2545 source_summary: None,
2546 }),
2547 ..CompileContext::new(CompileConfig::default())
2548 };
2549 let prepared = context
2550 .prepare_proxy(
2551 &GalleryItem::T
2552 .build()
2553 .flatten()
2554 .expect("gallery graph expands"),
2555 )
2556 .unwrap();
2557 let plan = crate::lower::LowerPlan::from_linked_input(&prepared.plan);
2558 let placed = crate::lower::PlacedPlan::new(PhysicalInput {
2559 graph: &prepared.graph,
2560 plan: &plan,
2561 compiled: &prepared.compiled,
2562 spatial_port_templates: &prepared.spatial_port_templates,
2563 spatial_ports: &prepared.spatial_ports,
2564 temporal_templates: &prepared.temporal_templates,
2565 wall_templates: &prepared.wall_templates,
2566 template_pool: &prepared.template_pool,
2567 layout: BlockLayout::new(3),
2568 })
2569 .unwrap();
2570 assert!(matches!(
2571 placed.emit(u32::MAX - 1),
2572 Err(CompileError::BooleanResource(error)) if error.resource == "observable IDs"
2573 ));
2574 }
2575
2576 #[test]
2577 fn compile_and_validate_normalizes_a_small_span_starting_at_i32_min() {
2578 let port = ivec3(0, 0, i32::MIN);
2579 let cube = ivec3(0, 0, i32::MIN + 1);
2580 let mut graph = BlockGraph::new();
2581 graph.add_block(Block::new(port, BlockKind::Port));
2582 graph.add_block(Block::new(cube, BlockKind::Cube(CubeKind::ZXZ)));
2583 graph.add_pipe(bloq_graph::Pipe::new(port, bloq_graph::Direction::ZPLUS));
2584 let ctx = CompileContext::new(CompileConfig::new(3));
2585
2586 ctx.compile_and_validate(&graph)
2587 .expect("the normalized z span is 0..=1");
2588 }
2589
2590 #[test]
2591 fn compile_propagates_block_layout_overflow() {
2592 assert!(matches!(
2593 block_xy_offset(IVec2::MAX, u32::MAX),
2594 Err(CompileError::BlockLayoutCoordinateOverflow { .. })
2595 ));
2596
2597 let graph = single_cube_runtime_graph()
2598 .shift_positions(ivec3(300_000_000, 0, 0))
2599 .expect("source coordinate fits");
2600 let stages = Arc::new(std::sync::Mutex::new(Vec::new()));
2601 let observed = Arc::clone(&stages);
2602 let ctx = CompileContext::new(CompileConfig::new(3))
2603 .with_progress_observer(move |stage| observed.lock().unwrap().push(stage));
2604
2605 assert!(matches!(
2606 ctx.compile(&graph),
2607 Err(CompileError::BlockLayoutCoordinateOverflow {
2608 block_xy,
2609 distance: 3,
2610 }) if block_xy == glam::ivec2(300_000_000, 0)
2611 ));
2612 assert!(!stages.lock().unwrap().contains(&CompileStage::Complete));
2613 }
2614
2615 #[test]
2616 fn boundary_observable_compile_returns_layout_overflow_without_panicking() {
2617 let graph = single_cube_runtime_graph()
2618 .shift_positions(ivec3(i32::MAX, 0, 0))
2619 .expect("boundary source coordinate fits");
2620 let ctx = CompileContext::new(CompileConfig::new(3));
2621
2622 assert!(matches!(
2623 ctx.compile(&graph),
2624 Err(CompileError::BlockLayoutCoordinateOverflow {
2625 block_xy,
2626 distance: 3,
2627 }) if block_xy == glam::ivec2(i32::MAX, 0)
2628 ));
2629 }
2630
2631 #[test]
2632 fn compile_context_reuses_templates_across_compile_calls() {
2633 let first_graph = single_cube_runtime_graph();
2634 let second_graph = single_cube_runtime_graph();
2635 let ctx = CompileContext::new(CompileConfig::new(3));
2636
2637 let first = ctx.compile(&first_graph).expect("compile first graph");
2638 let second = ctx.compile(&second_graph).expect("compile second graph");
2639
2640 assert_eq!(
2641 ctx.cache.pool_len(),
2642 1,
2643 "matching block signatures should reuse a cached template across compiles"
2644 );
2645 assert!(
2646 std::ptr::eq(
2647 first.bloq.templates().iter().next().unwrap().1,
2648 second.bloq.templates().iter().next().unwrap().1,
2649 ),
2650 "emitted programs retain the immutable cached template"
2651 );
2652 }
2653
2654 #[test]
2655 fn cancellation_stops_an_active_stage_and_the_next_request_can_use_the_cache() {
2656 let graph = GalleryItem::CNOT.build();
2657 let cache = crate::SharedCompileCache::new();
2658 for _ in 0..2 {
2659 let token = bloq_graph::CancellationToken::new();
2660 let cancel = token.clone();
2661 let context = CompileContext::with_shared_cache(CompileConfig::default(), &cache)
2662 .with_cancellation(token)
2663 .with_progress_observer(move |stage| {
2664 if stage == CompileStage::Readouts {
2665 cancel.cancel();
2666 }
2667 });
2668 let error = context.compile(&graph).unwrap_err();
2669 assert!(error.is_cancelled());
2670 assert!(!error.is_resource_limited());
2671 }
2672 let fresh = CompileContext::with_shared_cache(CompileConfig::default(), &cache)
2673 .with_cancellation(bloq_graph::CancellationToken::new());
2674 fresh.compile(&graph).unwrap();
2675 }
2676
2677 #[test]
2678 fn cancelled_module_request_does_not_publish_a_partial_cached_object() {
2679 let program = GalleryItem::CNOT.build().with_inferred_interface().unwrap();
2680 let cache = crate::SharedCompileCache::new();
2681 let token = bloq_graph::CancellationToken::new();
2682 let cancel = token.clone();
2683 let context = CompileContext::with_shared_cache(CompileConfig::default(), &cache)
2684 .with_cancellation(token)
2685 .with_progress_observer(move |stage| {
2686 if stage == CompileStage::Readouts {
2687 cancel.cancel();
2688 }
2689 });
2690 assert!(context.compile_object(&program).unwrap_err().is_cancelled());
2691 let stages = Arc::new(std::sync::Mutex::new(Vec::new()));
2692 let observed = Arc::clone(&stages);
2693 let fresh = CompileContext::with_shared_cache(CompileConfig::default(), &cache)
2694 .with_progress_observer(move |stage| observed.lock().unwrap().push(stage));
2695 fresh.compile_object(&program).unwrap();
2696 assert!(
2697 stages
2698 .lock()
2699 .unwrap()
2700 .contains(&CompileStage::Certification)
2701 );
2702 assert!(!stages.lock().unwrap().contains(&CompileStage::CacheReuse));
2703 }
2704
2705 #[test]
2706 fn progress_observer_reports_native_stages_and_completion() {
2707 let stages = Arc::new(std::sync::Mutex::new(Vec::new()));
2708 let observed = Arc::clone(&stages);
2709 let context = CompileContext::new(CompileConfig::default())
2710 .with_progress_observer(move |stage| observed.lock().unwrap().push(stage));
2711 context
2712 .compile(
2713 &GalleryItem::CNOT
2714 .build()
2715 .flatten()
2716 .expect("gallery graph expands"),
2717 )
2718 .unwrap();
2719 assert_eq!(
2720 *stages.lock().unwrap(),
2721 [
2722 CompileStage::Validation,
2723 CompileStage::Correlations,
2724 CompileStage::Placement,
2725 CompileStage::Templates,
2726 CompileStage::Readouts,
2727 CompileStage::Optimization,
2728 CompileStage::Complete,
2729 ]
2730 );
2731 }
2732
2733 #[test]
2734 fn progress_observer_reports_module_cache_reuse() {
2735 let module = single_cube_runtime_graph()
2736 .with_inferred_interface()
2737 .unwrap();
2738 let stages = Arc::new(std::sync::Mutex::new(Vec::new()));
2739 let observed = Arc::clone(&stages);
2740 let context = CompileContext::new(CompileConfig::default())
2741 .with_progress_observer(move |stage| observed.lock().unwrap().push(stage));
2742 context.compile(&module).unwrap();
2743 assert_eq!(
2744 stages
2745 .lock()
2746 .unwrap()
2747 .iter()
2748 .filter(|&&stage| stage == CompileStage::Complete)
2749 .count(),
2750 1
2751 );
2752 stages.lock().unwrap().clear();
2753 context.compile(&module).unwrap();
2754 assert_eq!(
2755 *stages.lock().unwrap(),
2756 [
2757 CompileStage::Validation,
2758 CompileStage::CacheReuse,
2759 CompileStage::Complete,
2760 ]
2761 );
2762 }
2763
2764 #[test]
2765 fn fixed_measurement_lowers_without_a_region_and_emits_stim() {
2766 let mut graph = BlockGraph::new();
2767 graph.add_block(Block::new(IVec3::ZERO, BlockKind::Cube(CubeKind::ZXZ)));
2768 graph.add_block(Block::new(IVec3::Z, BlockKind::Measurement(Basis::X)));
2769 graph.add_pipe(Pipe::new(IVec3::ZERO, Direction::ZPLUS));
2770
2771 let program = CompileContext::new(CompileConfig::new(3))
2772 .compile(&graph)
2773 .expect("fixed measurement graph compiles")
2774 .bloq;
2775
2776 program.validate().expect("compiled Bloq validates");
2777 assert!(program.nodes().all(|(_, node)| node.try_region().is_none()));
2778 let stim = bloq_stim::emit_bloq_stim(&program).expect("fixed measurement emits Stim");
2779 assert!(
2780 stim.contains("MX "),
2781 "expected transversal X readout:\n{stim}"
2782 );
2783 }
2784
2785 fn stacked_memory_graph(pipe: bloq_graph::Pipe) -> BlockGraph {
2788 let mut graph = BlockGraph::new();
2789 graph.add_block(Block::new(ivec3(0, 0, 0), BlockKind::Cube(CubeKind::ZXZ)));
2790 graph.add_block(Block::new(ivec3(0, 0, 1), BlockKind::Cube(CubeKind::ZXZ)));
2791 graph.add_pipe(pipe);
2792 graph
2793 }
2794
2795 #[test]
2800 fn reversed_temporal_pipe_orientation_compiles_identically() {
2801 let forward = stacked_memory_graph(bloq_graph::Pipe::new(
2802 ivec3(0, 0, 0),
2803 bloq_graph::Direction::ZPLUS,
2804 ));
2805 let reversed = stacked_memory_graph(bloq_graph::Pipe::new(
2806 ivec3(0, 0, 1),
2807 bloq_graph::Direction::ZMINUS,
2808 ));
2809
2810 let compile = |graph: &BlockGraph| {
2811 CompileContext::new(CompileConfig::new(3))
2812 .compile(graph)
2813 .expect("orientation compiles")
2814 .bloq
2815 .to_binary()
2816 };
2817 let forward_bytes = compile(&forward);
2818 let reversed_bytes = compile(&reversed);
2819
2820 assert_eq!(
2821 forward_bytes, reversed_bytes,
2822 "reversed temporal-pipe authoring must not change the compiled artifact"
2823 );
2824 }
2825
2826 #[test]
2827 fn basisless_temporal_hadamard_compiles_in_both_orientations() {
2828 let compile = |pipe| {
2829 let mut graph = BlockGraph::new();
2830 graph.add_block(Block::new(IVec3::ZERO, BlockKind::Port));
2831 graph.add_block(Block::new(IVec3::Z, BlockKind::Port));
2832 graph.add_pipe(pipe);
2833
2834 let bloq = super::compile(&graph, 3).expect("temporal Hadamard compiles");
2835 bloq.validate().expect("compiled Bloq validates");
2836 bloq.to_binary()
2837 };
2838 let forward = compile(Pipe::new(IVec3::ZERO, Direction::ZPLUS).with_hadamard());
2839 let reversed = compile(Pipe::new(IVec3::Z, Direction::ZMINUS).with_hadamard());
2840
2841 assert_eq!(forward, reversed);
2842 }
2843
2844 #[test]
2845 fn clifford_proxy_derives_exact_output_frames() {
2846 for (gallery, pins) in [(GalleryItem::CNOT, &[][..]), (GalleryItem::T, &[false][..])] {
2847 let bloq = compile_clifford_proxy(
2848 CompileConfig::default(),
2849 &gallery.build().flatten().expect("gallery graph expands"),
2850 pins,
2851 )
2852 .unwrap_or_else(|error| panic!("{gallery:?} proxy compiles: {error}"))
2853 .bloq;
2854 assert_eq!(
2855 bloq.output_frames().len(),
2856 bloq.logical_outputs().len(),
2857 "{gallery:?} proxy emits one frame pair per logical output",
2858 );
2859 bloq.validate()
2860 .expect("proxy ports and recipe owners validate");
2861 let flips = bloq
2862 .edges()
2863 .filter(|edge| {
2864 matches!(
2865 edge.edge,
2866 bloq_ir::BloqEdge::Value {
2867 output: bloq_ir::ObservableOutput::Flip,
2868 ..
2869 }
2870 )
2871 })
2872 .collect::<Vec<_>>();
2873 assert!(
2874 !flips.is_empty(),
2875 "proxy frames consume public decoder flips"
2876 );
2877 for edge in flips {
2878 assert!(matches!(
2879 bloq[edge.source].try_classical(),
2880 Some(bloq_ir::ClassicalNode::Observable { index: Some(_), .. })
2881 ));
2882 assert!(matches!(
2883 bloq[edge.target].try_classical(),
2884 Some(bloq_ir::ClassicalNode::Observable { index: None, .. })
2885 ));
2886 }
2887 }
2888 }
2889
2890 #[test]
2891 fn random_clifford_proxy_is_seeded_and_records_its_seed() {
2892 let hierarchical = GalleryItem::T.build();
2893 for graph in [hierarchical.flatten().unwrap(), hierarchical] {
2894 let compile = || {
2895 compile_random_clifford_proxy(CompileConfig::default(), &graph, 0xC10F_F0AD)
2896 .expect("random proxy compiles")
2897 .bloq
2898 };
2899 let first = compile();
2900 let second = compile();
2901 assert_eq!(first.to_binary(), second.to_binary());
2902 assert_eq!(
2903 first.metadata().get(CLIFFORD_PROXY_SEED_METADATA_KEY),
2904 Some(&bloq_ir::MetadataValue::U64(0xC10F_F0AD))
2905 );
2906 bloq_stim::emit_bloq_stim(&first).expect("random proxy is static Stim");
2907 }
2908 }
2909
2910 #[test]
2911 fn distance_proxy_replaces_t_even_when_mpp_preparation_is_enabled() {
2912 let bloq = compile_clifford_proxy(
2913 CompileConfig::default().with_prepare_t_with_mpps(true),
2914 &GalleryItem::T
2915 .build()
2916 .flatten()
2917 .expect("gallery graph expands"),
2918 &[false],
2919 )
2920 .expect("MPP-configured T proxy compiles")
2921 .bloq;
2922
2923 assert!(bloq.nodes().all(|(_, node)| node.try_region().is_none()));
2924 bloq_stim::emit_bloq_stim(&bloq).expect("MPP-configured T proxy is static Stim");
2925 }
2926
2927 #[test]
2928 fn clifford_proxy_accepts_partial_padding() {
2929 let mut bloq = compile_clifford_proxy(
2930 CompileConfig::default(),
2931 &GalleryItem::T
2932 .build()
2933 .flatten()
2934 .expect("gallery graph expands"),
2935 &[false],
2936 )
2937 .expect("T proxy compiles")
2938 .bloq;
2939 let target = bloq
2940 .nodes()
2941 .find_map(|(id, node)| {
2942 node.block_members()
2943 .iter()
2944 .any(|member| member.pos == ivec3(1, 0, 2))
2945 .then_some(id)
2946 })
2947 .expect("pinned selective node exists");
2948 let source = bloq
2949 .incoming(target)
2950 .find_map(|edge| {
2951 matches!(edge.edge, bloq_ir::BloqEdge::Quantum(_)).then_some(edge.source)
2952 })
2953 .expect("pinned selective seam exists");
2954
2955 let padding = bloq
2956 .insert_memory_rounds(
2957 MemoryRoundTarget::Edge {
2958 path: LevelPath::default(),
2959 from: source,
2960 to: target,
2961 },
2962 10,
2963 )
2964 .expect("pinned selective seam accepts decoder wait");
2965
2966 assert_eq!(bloq[padding].memory_rounds(), Some(10));
2967 bloq.validate().expect("padded proxy validates");
2968 bloq_stim::emit_bloq_stim(&bloq).expect("padded proxy emits");
2969 }
2970
2971 #[test]
2972 fn clifford_proxy_rejects_wrong_selective_pin_count() {
2973 let mut graph = BlockGraph::new();
2974 graph.add_block(Block::new(
2975 IVec3::ZERO,
2976 BlockKind::Selective(bloq_graph::SelectiveKind::XY),
2977 ));
2978
2979 assert!(matches!(
2980 compile_clifford_proxy(CompileConfig::default(), &graph, &[]),
2981 Err(CompileError::CliffordProxyPinCount {
2982 expected: 1,
2983 actual: 0
2984 })
2985 ));
2986 assert!(matches!(
2987 compile_clifford_proxy(CompileConfig::default(), &graph, &[false, true]),
2988 Err(CompileError::CliffordProxyPinCount {
2989 expected: 1,
2990 actual: 2
2991 })
2992 ));
2993 }
2994
2995 fn t_blocks_graph(positions: &[IVec3]) -> BlockGraph {
2997 let mut graph = BlockGraph::new();
2998 for &pos in positions {
2999 graph.add_block(Block::new(pos, BlockKind::T));
3000 graph.add_block(Block::new(pos + IVec3::Z, BlockKind::Cube(CubeKind::ZXZ)));
3001 graph.add_pipe(bloq_graph::Pipe::new(pos, bloq_graph::Direction::ZPLUS));
3002 }
3003 graph
3004 }
3005
3006 fn compiled_template_pool_len(graph: &BlockGraph) -> usize {
3009 let ctx = CompileContext::new(CompileConfig::new(3));
3010 let signatures = block_signatures(
3011 graph,
3012 &join_component_layer_schedules(graph).expect("no mixed spatial Hadamard walls"),
3013 ctx.config.code_distance(),
3014 )
3015 .expect("valid graph");
3016 let blocks = ordered_blocks(graph);
3017 ctx.compile_blocks(graph, &blocks, &signatures)
3018 .expect("compile blocks");
3019 ctx.cache.pool_len()
3020 }
3021
3022 #[test]
3023 fn t_blocks_with_matching_signatures_share_cached_templates() {
3024 let graph = t_blocks_graph(&[ivec3(0, 0, 0), ivec3(5, 0, 0)]);
3027 assert_eq!(compiled_template_pool_len(&graph), 3);
3028 }
3029
3030 #[test]
3031 fn t_blocks_with_differing_surgery_sides_compile_distinct_templates() {
3032 let mut graph = t_blocks_graph(&[ivec3(0, 0, 0), ivec3(5, 0, 0)]);
3036 graph.add_block(Block::new(ivec3(5, -1, 0), BlockKind::Cube(CubeKind::ZXZ)));
3037 assert_eq!(compiled_template_pool_len(&graph), 6);
3038 }
3039
3040 #[test]
3041 fn t_region_body_carries_seam_restarts_and_gap_wiring() {
3042 use bloq_ir::{BloqNodeKind, ClassicalExpr, ClassicalNode, ObservableOutput, RegionNode};
3043
3044 let graph = t_blocks_graph(&[ivec3(0, 0, 0)]);
3045 let ctx = CompileContext::new(CompileConfig::new(3));
3046 let program = ctx.compile(&graph).expect("T graph compiles").bloq;
3047 program.validate().expect("retry wiring is valid IR");
3048
3049 let (body, condition, source) = program
3050 .nodes()
3051 .find_map(|(_, node)| match &node.kind {
3052 BloqNodeKind::Region(RegionNode::RepeatUntilSuccess {
3053 body,
3054 restart_condition,
3055 restart_source,
3056 }) => Some((
3057 body,
3058 restart_condition,
3059 restart_source.expect("explicit body retry predicate"),
3060 )),
3061 _ => None,
3062 })
3063 .expect("the T block lowers to one RUS region");
3064
3065 let quantum: Vec<_> = body.quantum_nodes().map(|(_, node)| node).collect();
3070 assert_eq!(quantum.len(), 2, "cultivation + escape stage nodes");
3071 let (cultivation, escape) = (quantum[0], quantum[1]);
3072 let cultivation_id = cultivation.instances[0].id;
3073 let escape_id = escape.instances[0].id;
3074 assert!(cultivation.restarts.is_empty());
3075 assert!(!escape.restarts.is_empty(), "intra-body seam restarts");
3076 assert!(
3077 escape.restarts.iter().any(|restart| {
3078 let instances: crate::FxSet<_> = restart
3079 .parity
3080 .measurements()
3081 .map(|measurement| measurement.instance)
3082 .collect();
3083 instances.contains(&cultivation_id) && instances.contains(&escape_id)
3084 }),
3085 "a cross-template restart chain spans both stage instances"
3086 );
3087
3088 let mut indices = body
3089 .nodes()
3090 .filter_map(|(_, node)| match node.try_classical() {
3091 Some(ClassicalNode::Observable {
3092 index: Some(index),
3093 measurements,
3094 operators,
3095 }) => {
3096 assert!(
3097 measurements
3098 .iter()
3099 .all(|record| record.instance == escape_id)
3100 );
3101 assert!(
3102 operators
3103 .iter()
3104 .all(|operator| operator.instance == escape_id)
3105 );
3106 assert!(!measurements.is_empty());
3107 Some(*index)
3108 }
3109 _ => None,
3110 })
3111 .collect::<Vec<_>>();
3112 indices.sort_unstable();
3113 assert_eq!(indices.len(), 2, "two GAP decoding problems");
3114 assert_ne!(indices[0], indices[1]);
3115 let computes = body
3116 .nodes()
3117 .filter_map(|(id, node)| match node.try_classical() {
3118 Some(ClassicalNode::Compute { expr }) => Some((id, expr)),
3119 _ => None,
3120 })
3121 .collect::<Vec<_>>();
3122 assert_eq!(computes.len(), 1, "one explicit retry predicate");
3123 let (retry, expr) = computes[0];
3124 assert_eq!(source.node, retry);
3125 assert_eq!(source.output, ObservableOutput::Corrected);
3126 assert_ne!(
3127 body.value_output(),
3128 Some(source),
3129 "retry is not the body result"
3130 );
3131 assert_eq!(*condition, ClassicalExpr::In(0));
3132 let mut inputs = body.value_inputs(retry).collect::<Vec<_>>();
3133 inputs.sort_unstable_by_key(|input| input.slot);
3134 assert_eq!(
3135 inputs.iter().map(|input| input.slot).collect::<Vec<_>>(),
3136 [0, 1]
3137 );
3138 assert_ne!(inputs[0].producer, inputs[1].producer);
3139 for input in inputs {
3140 assert_eq!(input.output, Some(ObservableOutput::Flip));
3141 assert!(matches!(
3142 body[input.producer].try_classical(),
3143 Some(ClassicalNode::Observable { index: Some(_), .. })
3144 ));
3145 }
3146 for (flips, expected) in [
3147 ([false, false], false),
3148 ([false, true], true),
3149 ([true, false], true),
3150 ([true, true], true),
3151 ] {
3152 let retry = expr
3153 .eval(&mut |slot| flips.get(slot as usize).copied())
3154 .unwrap();
3155 assert_eq!(retry, expected, "decoder flips {flips:?}");
3156 assert_eq!(
3157 condition.eval(&mut |slot| (slot == 0).then_some(retry)),
3158 Some(expected)
3159 );
3160 }
3161
3162 let top_quantum: Vec<_> = program.quantum_nodes().map(|(_, node)| node).collect();
3166 assert!(top_quantum.iter().all(|node| node.restarts.is_empty()));
3167 assert!(
3168 top_quantum.iter().any(|node| {
3169 program.node_detectors(node).unwrap().any(|detector| {
3170 detector
3171 .measurements()
3172 .any(|measurement| measurement.instance == escape_id)
3173 })
3174 }),
3175 "the neighbour's seam detectors reference the in-body escape instance"
3176 );
3177 }
3178
3179 #[test]
3180 fn boxed_t_block_needs_free_neighbor_end_to_end() {
3181 let mut graph = t_blocks_graph(&[ivec3(0, 0, 0)]);
3182 for neighbor in [
3183 ivec3(0, -1, 0),
3184 ivec3(1, 0, 0),
3185 ivec3(0, 1, 0),
3186 ivec3(-1, 0, 0),
3187 ] {
3188 graph.add_block(Block::new(neighbor, BlockKind::Cube(CubeKind::ZXZ)));
3189 }
3190 let ctx = CompileContext::new(CompileConfig::new(3));
3191 assert!(matches!(
3192 ctx.compile_and_validate(&graph).unwrap_err(),
3193 CompileError::TBlockNeedsFreeNeighbor { .. }
3194 ));
3195 assert!(matches!(
3196 ctx.compile(&graph).unwrap_err(),
3197 CompileError::TBlockNeedsFreeNeighbor { .. }
3198 ));
3199 }
3200
3201 #[test]
3202 fn compile_t_block_lowers_to_repeat_until_success_region() {
3203 use bloq_ir::{BloqNodeKind, RegionNode};
3204
3205 let graph = t_blocks_graph(&[ivec3(0, 0, 0)]);
3206 let ctx = CompileContext::new(CompileConfig::new(3));
3207
3208 let program = ctx.compile(&graph).expect("T graph compiles").bloq;
3209
3210 let rus_count = program
3211 .nodes()
3212 .filter(|(_, node)| {
3213 matches!(
3214 node.kind,
3215 BloqNodeKind::Region(RegionNode::RepeatUntilSuccess { .. })
3216 )
3217 })
3218 .count();
3219 assert_eq!(rus_count, 1, "the T block is one RepeatUntilSuccess region");
3220
3221 program.validate().expect("T program validates recursively");
3223
3224 let error = bloq_stim::emit_bloq_stim(&program).expect_err("RUS is backend-rejected");
3227 assert!(
3228 matches!(error, bloq_stim::StimEmissionError::UnsupportedNode(reason) if reason.contains("RepeatUntilSuccess")),
3229 "expected RepeatUntilSuccess rejection, got: {error:?}",
3230 );
3231 }
3232
3233 #[test]
3234 fn registry_closed_readouts_keep_their_code_distance() {
3235 for gallery in [
3236 GalleryItem::XMemory,
3237 GalleryItem::YMemory,
3238 GalleryItem::Stability,
3239 ] {
3240 let context = CompileContext::new(CompileConfig::default());
3241 let bloq = registry::compile_graph(
3242 &context,
3243 &gallery.build().flatten().expect("gallery graph expands"),
3244 )
3245 .unwrap()
3246 .bloq;
3247 let noise = stim::noise::UniformDepolarizing::new(1e-3).unwrap();
3248 let circuit: stim::Circuit = bloq_stim::emit_bloq_stim_with_stim_noise(&bloq, &noise)
3249 .unwrap()
3250 .parse()
3251 .unwrap();
3252 assert_eq!(circuit.num_observables(), 1, "{gallery:?}");
3253 assert_eq!(
3254 circuit.shortest_graphlike_error().unwrap().len(),
3255 3,
3256 "{gallery:?}"
3257 );
3258 }
3259 }
3260
3261 #[test]
3262 fn registry_preparations_do_not_decode_unavailable_output_axes() {
3263 for gallery in [GalleryItem::BellState, GalleryItem::GHZ] {
3264 let graph = gallery.build().flatten().expect("gallery graph expands");
3265 let expected = bloq_graph::ZXGraph::from_block_graph_for_analysis(&graph)
3266 .unwrap()
3267 .stabilizers()
3268 .unwrap()
3269 .generators
3270 .len();
3271 let context = CompileContext::new(CompileConfig::default());
3272 for bloq in [
3273 registry::compile_graph(&context, &graph).unwrap().bloq,
3274 registry::compile_hierarchy(&context, &gallery.build(), NonZeroUsize::MIN)
3275 .unwrap()
3276 .bloq,
3277 ] {
3278 let noise = stim::noise::UniformDepolarizing::new(1e-3).unwrap();
3279 let circuit: stim::Circuit =
3280 bloq_stim::emit_bloq_stim_with_stim_noise(&bloq, &noise)
3281 .unwrap()
3282 .parse()
3283 .unwrap();
3284 assert_eq!(circuit.num_observables(), expected as u64, "{gallery:?}");
3285 assert_eq!(circuit.shortest_graphlike_error().unwrap().len(), 3);
3286 }
3287 }
3288 }
3289
3290 #[test]
3291 fn empty_native_programs_compile_and_validate() {
3292 let context = CompileContext::new(CompileConfig::default());
3293 let graph = BlockGraph::new();
3294 for bloq in [
3295 context.compile_and_validate(&graph).unwrap().bloq,
3296 context
3297 .compile(&graph.with_inferred_interface().unwrap())
3298 .unwrap()
3299 .bloq,
3300 ] {
3301 bloq.validate().unwrap();
3302 assert_eq!(bloq.node_count(), 0);
3303 }
3304 }
3305
3306 #[test]
3307 fn t_gallery_programs_compile_validate_and_are_backend_rejected() {
3308 for gallery in [
3314 GalleryItem::T,
3315 GalleryItem::TWithPreparedY,
3316 GalleryItem::PhaseGradientK4,
3317 GalleryItem::THTH,
3318 ] {
3319 let graph = gallery.build().flatten().expect("gallery graph expands");
3320 {
3324 let code_distance = 3;
3325 let ctx = CompileContext::new(CompileConfig::new(code_distance));
3326 let program = ctx
3327 .compile(&graph)
3328 .unwrap_or_else(|error| {
3329 panic!("{gallery:?} d={code_distance} compiles: {error}")
3330 })
3331 .bloq;
3332 program.validate().unwrap_or_else(|error| {
3333 panic!("{gallery:?} d={code_distance} validates: {error}")
3334 });
3335 let error = bloq_stim::emit_bloq_stim(&program)
3336 .expect_err("T programs are backend-rejected");
3337 assert!(
3338 matches!(&error, bloq_stim::StimEmissionError::UnsupportedNode(_)),
3339 "{gallery:?} d={code_distance}: expected region rejection, got {error:?}",
3340 );
3341 }
3342 }
3343 }
3344
3345 #[test]
3346 fn phase_gradient_records_padding_before_selective_components() {
3347 use bloq_ir::BloqEdge;
3348
3349 let ctx = CompileContext::new(CompileConfig::new(3));
3350 let program = ctx
3351 .compile(
3352 &GalleryItem::PhaseGradientK4
3353 .build()
3354 .flatten()
3355 .expect("gallery graph expands"),
3356 )
3357 .expect("phase gradient compiles")
3358 .bloq;
3359 let mut seams = 0;
3360 for (branch, node) in program.nodes() {
3361 if !node
3362 .try_quantum()
3363 .is_some_and(|quantum| !quantum.guards.is_empty())
3364 {
3365 continue;
3366 }
3367 for edge in program.incoming(branch) {
3368 let BloqEdge::Quantum(quantum) = edge.edge else {
3369 continue;
3370 };
3371 seams += 1;
3372 assert!(
3373 quantum.pipes.iter().all(|seam| seam.padding.is_some()),
3374 "selective input {edge:?} must persist a padding template per pipe",
3375 );
3376 }
3377 }
3378 assert!(seams > 0, "phase gradient contains adaptive quantum seams");
3379 }
3380
3381 #[rstest]
3387 #[case(GalleryItem::CNOT)]
3388 #[case(GalleryItem::S)]
3389 #[case(GalleryItem::T)]
3390 fn exchange_formats_round_trip(#[case] gallery: GalleryItem) {
3391 let ctx = CompileContext::new(CompileConfig::new(3));
3392 let program = ctx
3393 .compile(&gallery.build().flatten().expect("gallery graph expands"))
3394 .expect("compiles")
3395 .bloq;
3396
3397 let bytes = program.to_binary();
3398 let from_binary = bloq_ir::Bloq::from_binary(&bytes).expect("binary decodes");
3399 assert_eq!(
3400 bytes,
3401 from_binary.to_binary(),
3402 "{gallery:?}: binary fixpoint"
3403 );
3404
3405 let text = program.to_text();
3406 let from_text = bloq_ir::Bloq::from_text(&text)
3407 .unwrap_or_else(|error| panic!("{gallery:?}: text parses: {error}"));
3408 assert_eq!(
3409 bytes,
3410 from_text.to_binary(),
3411 "{gallery:?}: text round trip is structurally lossless"
3412 );
3413 assert_eq!(text, from_text.to_text(), "{gallery:?}: text fixpoint");
3414
3415 from_text
3416 .validate()
3417 .unwrap_or_else(|error| panic!("{gallery:?}: restored program validates: {error}"));
3418
3419 let expected = bloq_stim::emit_bloq_stim(&program);
3420 let restored = bloq_stim::emit_bloq_stim(&from_text);
3421 if gallery == GalleryItem::T {
3422 for result in [expected, restored] {
3423 assert!(matches!(
3424 result,
3425 Err(bloq_stim::StimEmissionError::UnsupportedNode(_))
3426 ));
3427 }
3428 } else {
3429 assert_eq!(
3430 expected.expect("static original emits"),
3431 restored.expect("static restored program emits"),
3432 "{gallery:?}: identical Stim emission"
3433 );
3434 }
3435 }
3436
3437 #[test]
3440 fn s_gallery_lowers_output_frames_from_symbolic_solve() {
3441 use bloq_ir::ClassicalNode;
3442
3443 let ctx = CompileContext::new(CompileConfig::default());
3444 let program = ctx
3445 .compile(
3446 &GalleryItem::S
3447 .build()
3448 .flatten()
3449 .expect("gallery graph expands"),
3450 )
3451 .unwrap()
3452 .bloq;
3453 let frames = program.output_frames();
3454 assert_eq!(frames.len(), 1, "S teleport has one output port");
3455
3456 let classical = |node: bloq_ir::BloqNodeId| {
3457 program[node]
3458 .try_classical()
3459 .expect("frame nodes are classical")
3460 .clone()
3461 };
3462 assert!(matches!(
3463 classical(frames[0].x),
3464 ClassicalNode::Compute { .. }
3465 ));
3466 assert!(matches!(
3467 classical(frames[0].z),
3468 ClassicalNode::Compute { .. }
3469 ));
3470
3471 let mut stack = vec![frames[0].z];
3474 let mut seen = std::collections::HashSet::new();
3475 let (mut saw_records, mut saw_observable) = (false, false);
3476 while let Some(node) = stack.pop() {
3477 if !seen.insert(node) {
3478 continue;
3479 }
3480 if let Some(classical) = program[node].try_classical() {
3481 saw_records |= !classical.measurements().is_empty();
3482 saw_observable |= matches!(classical, ClassicalNode::Observable { .. });
3483 }
3484 stack.extend(program.top().data_inputs(node).map(|input| input.producer));
3485 }
3486 assert!(
3487 saw_observable,
3488 "Z frame reads a member's corrected observable"
3489 );
3490 assert!(
3491 saw_records,
3492 "the observable recipe contains physical records"
3493 );
3494
3495 let ctx = CompileContext::new(CompileConfig::default());
3496 let action_free = ctx
3497 .compile(
3498 &GalleryItem::CNOT
3499 .build()
3500 .flatten()
3501 .expect("gallery graph expands"),
3502 )
3503 .unwrap()
3504 .bloq;
3505 assert_eq!(
3506 action_free.output_frames().len(),
3507 2,
3508 "action-free CNOT gets one FramePair per output port"
3509 );
3510 }
3511
3512 #[test]
3513 fn output_frame_ports_match_logical_outputs() {
3514 let compile = |graph: &BlockGraph| {
3515 CompileContext::new(CompileConfig::new(3))
3516 .compile(graph)
3517 .expect("gallery graph compiles")
3518 .bloq
3519 };
3520
3521 let cnot = compile(
3522 &GalleryItem::CNOT
3523 .build()
3524 .flatten()
3525 .expect("gallery graph expands"),
3526 );
3527 let frame_outputs: Vec<_> = cnot
3528 .output_frames()
3529 .into_iter()
3530 .map(|frame| frame.port)
3531 .collect();
3532 let logical_outputs: Vec<_> = cnot
3533 .logical_outputs()
3534 .iter()
3535 .map(|output| output.port)
3536 .collect();
3537 assert_eq!(frame_outputs, logical_outputs);
3538 assert!(frame_outputs.contains(&glam::ivec3(1, 1, 3)));
3539
3540 let shifted_t = GalleryItem::T
3541 .build()
3542 .flatten()
3543 .expect("gallery graph expands")
3544 .shift_positions(IVec3::X)
3545 .expect("shift fits");
3546 let t = compile(&shifted_t);
3547 assert_eq!(
3548 t.output_frames()
3549 .into_iter()
3550 .map(|frame| frame.port)
3551 .collect::<Vec<_>>(),
3552 t.logical_outputs()
3553 .iter()
3554 .map(|output| output.port)
3555 .collect::<Vec<_>>(),
3556 "selective-fill frames use the same source output ports"
3557 );
3558 }
3559
3560 #[test]
3561 fn compile_graph_lowers_single_cube() {
3562 let graph = single_cube_runtime_graph();
3563 let ctx = CompileContext::new(CompileConfig::new(3));
3564
3565 let artifacts = ctx.compile(&graph).unwrap();
3566 let program = artifacts.bloq;
3567
3568 assert_eq!(program.quantum_node_count(), 1);
3569 assert_eq!(program.templates().len(), 1);
3570 let node = program.quantum_nodes().next().unwrap().1;
3571 assert_eq!(node.instances.len(), 1);
3572 assert_eq!(node.instances[0].id.0, 0);
3573 assert_eq!(node.instances[0].template_id.0, 0);
3574 assert_eq!(node.timeline, None);
3575 assert!(program.nodes().any(|(_, node)| matches!(
3577 node.try_classical(),
3578 Some(bloq_ir::ClassicalNode::Observable { .. })
3579 )));
3580 }
3581
3582 fn selective_measurement_graph() -> BlockGraph {
3584 BlockGraph::from_blog_text(
3585 "BLOG 1.0\n\n 0: Port [0,0,-1]\n 1: ZXZ [0,0,0]\n 2: XZ [0,0,1]\n 3: ZXZ [1,0,0]\n [0,0,-1] -> +Z\n [0,0,0] -> +Z\n\n m2 = measure 3\n resolve 2 if m2\n",
3586 )
3587 .expect("selective measurement graph parses")
3588 }
3589
3590 #[test]
3591 fn compile_selective_block_lowers_to_guarded_component() {
3592 let graph = selective_measurement_graph();
3593 let ctx = CompileContext::new(CompileConfig::new(3));
3594 let program = ctx.compile(&graph).expect("selective graph compiles").bloq;
3595
3596 assert_eq!(program.quantum_node_count(), 4);
3597 assert!(program.nodes().all(|(_, node)| node.try_region().is_none()));
3598 assert!(ctx.cache.pool_len() >= 3);
3599 program.validate().unwrap();
3600 assert!(matches!(
3601 bloq_stim::emit_bloq_stim(&program),
3602 Err(bloq_stim::StimEmissionError::UnsupportedNode(_))
3603 ));
3604
3605 let selector_name = bloq_graph::selective_selector_name(ivec3(0, 0, 2));
3606 let mut arm_ids = Vec::new();
3607 for choice in [false, true] {
3608 let pinned = program
3609 .pin_membership(&std::collections::BTreeMap::from([(
3610 selector_name.clone(),
3611 choice,
3612 )]))
3613 .expect("both selective choices are reachable");
3614 assert!(!pinned.has_conditional_membership());
3615 let (owner, quantum) = pinned
3616 .quantum_nodes()
3617 .find(|(owner, _)| {
3618 pinned[*owner]
3619 .block_members()
3620 .iter()
3621 .any(|member| member.pos == ivec3(0, 0, 2))
3622 })
3623 .unwrap();
3624 assert_eq!(quantum.instances.len(), 1);
3625 arm_ids.push(quantum.instances[0].id);
3626 pinned[owner]
3627 .instantiate_circuit(pinned.templates())
3628 .unwrap();
3629
3630 let arm = quantum.instances[0].id;
3632 assert!(pinned.node_detector_count(quantum).unwrap() > 0);
3633 for detector in pinned.node_detectors(quantum).unwrap() {
3634 let instances = detector
3635 .measurements()
3636 .map(|record| record.instance)
3637 .collect::<Vec<_>>();
3638 assert!(instances.contains(&arm));
3639 assert!(instances.iter().any(|&instance| instance != arm));
3640 }
3641 }
3642 assert_ne!(arm_ids[0], arm_ids[1]);
3643 }
3644
3645 #[test]
3646 fn selective_pins_preserve_shared_compound_controls() {
3647 use std::collections::BTreeMap;
3648
3649 let graph = BlockGraph::from_blog_text(
3650 "BLOG 1.0\n\n 0: Port [0,0,0]\n 1: XZ [0,0,1]\n 2: Port [2,0,0]\n 3: XZ [2,0,1]\n 4: ZXZ [4,0,0]\n 5: ZXZ [6,0,0]\n [0,0,0] -> +Z\n [2,0,0] -> +Z\n\n a = measure 4\n b = measure 5\n resolve 1 if a ^ b\n resolve 3 if a ^ b\n",
3651 ).unwrap();
3652 let program = compile(&graph, 3).unwrap();
3653 let names = [ivec3(0, 0, 1), ivec3(2, 0, 1)].map(bloq_graph::selective_selector_name);
3654 for choice in [false, true] {
3655 let pinned = program
3656 .pin_membership(&BTreeMap::from([
3657 (names[0].clone(), choice),
3658 (names[1].clone(), choice),
3659 ]))
3660 .unwrap();
3661 assert!(!pinned.has_conditional_membership());
3662 pinned.validate().unwrap();
3663 }
3664 assert!(matches!(
3665 program.pin_membership(&BTreeMap::from([
3666 (names[0].clone(), false),
3667 (names[1].clone(), true),
3668 ])),
3669 Err(bloq_ir::MembershipPinError::UnreachableAssignment)
3670 ));
3671 }
3672
3673 fn selective_crossing_graph() -> BlockGraph {
3680 BlockGraph::from_blog_text(
3681 "BLOG 1.0\n\n 0: Port [0,0,0]\n 1: XZ [0,0,1]\n 2: ZXZ [1,0,0]\n [0,0,0] -> +Z\n\n m2 = measure 2\n resolve 1 if m2\n",
3682 )
3683 .expect("selective crossing graph parses")
3684 }
3685
3686 fn value_with_decode(program: &Bloq, id: bloq_ir::BloqNodeId, control: bool) -> bool {
3687 use bloq_ir::ClassicalNode;
3688
3689 match program[id].try_classical().expect("classical fold") {
3690 ClassicalNode::Observable { .. } => control,
3691 ClassicalNode::Compute { expr } => expr
3692 .eval(&mut |slot| {
3693 let input = program
3694 .top()
3695 .value_inputs(id)
3696 .find(|input| input.slot == slot)?;
3697 Some(value_with_decode(program, input.producer, control))
3698 })
3699 .expect("all fold inputs are available"),
3700 other => panic!("unexpected fold producer: {other:?}"),
3701 }
3702 }
3703
3704 #[test]
3705 fn feedback_folding_respects_the_resolve_condition() {
3706 use bloq_ir::{ClassicalNode, ValueRole};
3707
3708 let graph = BlockGraph::from_blog_text(
3709 "BLOG 1.0\n\n 0: Port [0,0,0]\n 1: XZ [0,0,1]\n 2: ZXZ [1,0,0]\n [0,0,0] -> +Z\n\n m2 = measure 2\n resolve 1 if m2\n feedback X 1 if m2\n",
3710 )
3711 .expect("delta-only feedback graph parses");
3712 let ctx = CompileContext::new(CompileConfig::new(3));
3713 let program = ctx
3714 .compile(&graph)
3715 .expect("delta-only feedback compiles")
3716 .bloq;
3717 let folds: Vec<_> = program.nodes()
3718 .filter(|(_, node)| matches!(node.try_classical(), Some(ClassicalNode::Observable { index, .. }) if *index != Some(0)))
3719 .flat_map(|(observable, _)| program.incoming(observable))
3720 .filter(|edge| matches!(edge.edge, bloq_ir::BloqEdge::Value { role: ValueRole::FeedbackFold { .. }, .. }))
3721 .collect();
3722
3723 for control in [false, true] {
3726 assert!(!folds.iter().fold(false, |odd, edge| {
3727 odd ^ value_with_decode(&program, edge.source, control)
3728 }));
3729 }
3730 }
3731
3732 #[test]
3735 fn selective_crossing_observable_reads_selected_arm() {
3736 use bloq_graph::Pauli;
3737 use bloq_ir::{BloqEdge, ClassicalNode};
3738 use std::collections::BTreeSet;
3739
3740 let program = CompileContext::new(CompileConfig::default())
3741 .compile(&selective_crossing_graph())
3742 .unwrap()
3743 .bloq;
3744 program.validate().unwrap();
3745 let (branch, quantum) = program
3746 .quantum_nodes()
3747 .find(|(_, quantum)| quantum.instances.len() == 2 && !quantum.guards.is_empty())
3748 .unwrap();
3749 let arms = [false, true].map(|control| {
3750 let selected = program[branch]
3751 .select_quantum_members(|slot| {
3752 let input = program
3753 .value_inputs(branch)
3754 .find(|input| input.slot == slot)?;
3755 Some(value_with_decode(&program, input.producer, control))
3756 })
3757 .unwrap();
3758 selected.expect_quantum().instances[0].id
3759 });
3760 assert_eq!(quantum.instances.len(), arms.len());
3761 for control in [false, true] {
3762 let mut selected = 0;
3763 for (observable, _) in program.nodes().filter(|(_, node)| {
3764 matches!(node.try_classical(), Some(ClassicalNode::Observable { index, .. }) if *index != Some(0))
3765 }) {
3766 let mut bases = Vec::new();
3767 let mut records = BTreeSet::new();
3768 for input in program.top().data_inputs(observable).filter(|input| input.output.is_none()) {
3769 let producer = input.producer;
3770 let node = &program[producer];
3771 if let Some(slot) = node.activation {
3772 let guard = program.value_inputs(producer).find(|input| input.slot == slot).unwrap().producer;
3773 if !value_with_decode(&program, guard, control) { continue; }
3774 }
3775 if let Some(ClassicalNode::Observable { measurements, operators, .. }) = node.try_classical() {
3776 bases.extend(operators.iter().flat_map(|operator| operator.operator.iter().map(|(_, pauli)| *pauli)));
3777 records.extend(measurements.iter().map(|record| record.instance));
3778 if measurements.iter().any(|record| arms.contains(&record.instance)) {
3779 assert!(program.incoming(producer).any(|edge| edge.source == branch && *edge.edge == BloqEdge::Order));
3780 }
3781 }
3782 }
3783 if bases.is_empty() { continue; }
3784 selected += 1;
3785 assert!(bases.iter().all(|&basis| basis == if control { Pauli::X } else { Pauli::Z }));
3786 assert!(records.contains(&arms[usize::from(control)]));
3787 assert!(!records.contains(&arms[usize::from(!control)]));
3788 }
3789 assert_eq!(selected, 1, "one selected input correlation per arm");
3790 }
3791 assert!(matches!(
3792 bloq_stim::emit_bloq_stim(&program),
3793 Err(bloq_stim::StimEmissionError::UnsupportedNode(_))
3794 ));
3795 }
3796
3797 #[test]
3798 fn compile_lowers_observable_to_classical_nodes() {
3799 use bloq_ir::{BloqEdge, ClassicalNode};
3800
3801 let graph = single_cube_runtime_graph();
3802 let ctx = CompileContext::new(CompileConfig::new(3));
3803 let program = ctx.compile(&graph).unwrap().bloq;
3804 let g = &program;
3805
3806 assert_eq!(program.quantum_node_count(), 1);
3807
3808 let observables = g
3809 .nodes()
3810 .filter_map(|(id, node)| match node.try_classical() {
3811 Some(ClassicalNode::Observable { measurements, .. })
3812 if !measurements.is_empty() =>
3813 {
3814 Some(id)
3815 }
3816 _ => None,
3817 })
3818 .collect::<Vec<_>>();
3819 assert!(
3820 !observables.is_empty(),
3821 "records belong directly to complete observables"
3822 );
3823 let order = program.deterministic_emit_order().expect("acyclic");
3824 let position = |id| order.iter().position(|&node| node == id).unwrap();
3825 for observable in observables {
3826 let owners = g
3827 .incoming(observable)
3828 .filter(|edge| {
3829 matches!(edge.edge, BloqEdge::Order) && g[edge.source].try_quantum().is_some()
3830 })
3831 .collect::<Vec<_>>();
3832 assert!(
3833 !owners.is_empty(),
3834 "the complete readout waits for producing quantum work"
3835 );
3836 for edge in owners {
3837 assert!(position(edge.source) < position(observable));
3838 }
3839 }
3840 }
3841
3842 #[test]
3843 fn try_new_rejects_invalid_distance() {
3844 assert_eq!(CompileConfig::try_new(2), Err(crate::InvalidDistance(2)));
3847 assert_eq!(CompileConfig::try_new(1), Err(crate::InvalidDistance(1)));
3848 CompileConfig::try_new(3).unwrap();
3849 }
3850
3851 #[test]
3852 fn compile_preserves_scaled_cube_top_temporal_pipe_ref() {
3853 let mut graph = BlockGraph::new();
3854 graph.add_block(
3855 bloq_graph::Block::new(ivec3(0, 0, 0), BlockKind::Cube(CubeKind::ZXZ))
3856 .with_height("2d".parse().expect("valid height"))
3857 .unwrap(),
3858 );
3859 graph.add_block(bloq_graph::Block::new(
3860 ivec3(0, 0, 2),
3861 BlockKind::Cube(CubeKind::ZXZ),
3862 ));
3863 graph.add_pipe(bloq_graph::Pipe::new(
3864 ivec3(0, 0, 1),
3865 bloq_graph::Direction::ZPLUS,
3866 ));
3867 let ctx = CompileContext::new(CompileConfig::new(3));
3868
3869 let artifacts = ctx.compile(&graph).expect("scaled temporal graph compiles");
3870 let pipes = artifacts
3871 .bloq
3872 .edges()
3873 .flat_map(|edge| edge.edge.pipes().iter())
3874 .collect::<Vec<_>>();
3875
3876 assert_eq!(artifacts.bloq.quantum_node_count(), 2);
3877 assert_eq!(pipes.len(), 1);
3878 assert_eq!(pipes[0].pipe.src, ivec3(0, 0, 1));
3879 assert_eq!(pipes[0].pipe.dst, ivec3(0, 0, 2));
3880 assert_eq!(
3881 source_timeline(&artifacts.bloq, ivec3(0, 0, 0)),
3882 Some(&bloq_ir::QuantumTimeline {
3883 layer_round_ends: vec![3, 6],
3884 })
3885 );
3886 }
3887
3888 #[test]
3889 fn tall_cube_timeline_packs_from_the_connected_side() {
3890 let tall = |pos| {
3891 Block::new(pos, BlockKind::Cube(CubeKind::ZXZ))
3892 .with_height("3d/2".parse().expect("valid height"))
3893 .expect("height fits")
3894 };
3895
3896 let mut high_anchored = BlockGraph::new();
3897 high_anchored.add_block(tall(IVec3::ZERO));
3898 let high = compile(&high_anchored, 5).expect("isolated tall cube compiles");
3899 assert_eq!(
3900 source_timeline(&high, IVec3::ZERO),
3901 Some(&bloq_ir::QuantumTimeline {
3902 layer_round_ends: vec![3, 8],
3903 })
3904 );
3905
3906 let mut low_anchored = BlockGraph::new();
3907 low_anchored.add_block(Block::new(IVec3::ZERO, BlockKind::Port));
3908 low_anchored.add_block(tall(IVec3::Z));
3909 low_anchored.add_pipe(Pipe::new(IVec3::ZERO, Direction::ZPLUS));
3910 let low = compile(&low_anchored, 5).expect("connected tall cube compiles");
3911 assert_eq!(
3912 source_timeline(&low, IVec3::Z),
3913 Some(&bloq_ir::QuantumTimeline {
3914 layer_round_ends: vec![5, 8],
3915 })
3916 );
3917 }
3918
3919 #[test]
3920 fn compile_graph_lowers_temporal_y_pair_with_inferred_basis() {
3921 let mut graph = BlockGraph::new();
3922 graph.add_block(bloq_graph::Block::new(ivec3(0, 0, 0), BlockKind::Y));
3923 graph.add_block(bloq_graph::Block::new(ivec3(0, 0, 1), BlockKind::Y));
3924 graph.add_pipe(bloq_graph::Pipe::new(
3925 ivec3(0, 0, 0),
3926 bloq_graph::Direction::ZPLUS,
3927 ));
3928 let ctx = CompileContext::new(CompileConfig::new(3));
3929
3930 let artifacts = ctx.compile(&graph).unwrap();
3931
3932 assert_eq!(artifacts.bloq.quantum_node_count(), 2);
3933 }
3934
3935 #[test]
3936 fn compile_context_outputs_only_graph_local_templates() {
3937 let unit_graph = single_cube_runtime_graph();
3938 let mut scaled_graph = BlockGraph::new();
3939 scaled_graph.add_block(
3940 bloq_graph::Block::new(ivec3(0, 0, 0), BlockKind::Cube(CubeKind::ZXZ))
3941 .with_height("2d".parse().expect("valid height"))
3942 .unwrap(),
3943 );
3944 let ctx = CompileContext::new(CompileConfig::new(3));
3945
3946 ctx.compile(&unit_graph).expect("compile unit cube");
3947 let artifacts = ctx.compile(&scaled_graph).expect("compile scaled cube");
3948
3949 assert_eq!(ctx.cache.pool_len(), 2);
3950 assert_eq!(artifacts.bloq.templates().len(), 1);
3951 let (_, node) = artifacts.bloq.nodes().next().unwrap();
3952 assert_eq!(node.expect_quantum().instances[0].template_id.0, 0);
3953 }
3954
3955 const SHARED_CACHE_CASES: [GalleryItem; 4] = [
3959 GalleryItem::CNOT,
3960 GalleryItem::XMemory,
3961 GalleryItem::S,
3962 GalleryItem::T,
3963 ];
3964
3965 fn compile_to_binary(ctx: &CompileContext, graph: &BlockGraph) -> Vec<u8> {
3966 ctx.compile(graph)
3967 .expect("gallery graph compiles")
3968 .bloq
3969 .to_binary()
3970 }
3971
3972 #[test]
3976 fn shared_cache_compiles_byte_identically_to_a_private_context() {
3977 let config = CompileConfig::new(3);
3978 let cache = crate::SharedCompileCache::new();
3979
3980 for item in SHARED_CACHE_CASES {
3981 let graph = item.build().flatten().expect("gallery graph expands");
3982 let private = compile_to_binary(&CompileContext::new(config), &graph);
3983 let warming = CompileContext::with_shared_cache(config, &cache);
3985 compile_to_binary(&warming, &graph);
3986 let shared =
3987 compile_to_binary(&CompileContext::with_shared_cache(config, &cache), &graph);
3988
3989 assert_eq!(private, shared, "{item:?} compiles identically when shared");
3990 }
3991 }
3992
3993 fn capped_cube(height: &str, cells: i32) -> BlockGraph {
4001 let mut graph = BlockGraph::new();
4002 graph.add_block(
4003 bloq_graph::Block::new(ivec3(0, 0, 0), BlockKind::Cube(CubeKind::ZXZ))
4004 .with_height(height.parse().expect("valid height"))
4005 .expect("cube accepts a height"),
4006 );
4007 graph.add_block(bloq_graph::Block::new(ivec3(0, 0, cells), BlockKind::Port));
4008 graph.add_pipe(bloq_graph::Pipe::new(
4009 ivec3(0, 0, cells - 1),
4010 bloq_graph::Direction::ZPLUS,
4011 ));
4012 graph
4013 }
4014
4015 #[test]
4018 fn cube_templates_may_share_rounds_across_different_footprints() {
4019 const DISTANCE: u32 = 5;
4020 let config = CompileConfig::new(DISTANCE);
4021 let two_cell = capped_cube("2d", 2);
4022 let one_cell = capped_cube("d+5", 1);
4023 let signature_of = |graph: &BlockGraph| {
4024 let schedules =
4025 join_component_layer_schedules(graph).expect("no mixed spatial Hadamard walls");
4026 block_signatures(graph, &schedules, DISTANCE).expect("valid graph")[&ivec3(0, 0, 0)]
4027 };
4028 assert_eq!(signature_of(&two_cell), signature_of(&one_cell));
4029
4030 let private = compile_to_binary(&CompileContext::new(config), &one_cell);
4031 let cache = crate::SharedCompileCache::new();
4032 compile_to_binary(
4033 &CompileContext::with_shared_cache(config, &cache),
4034 &two_cell,
4035 );
4036 let shared = CompileContext::with_shared_cache(config, &cache);
4037 let warmed = shared.cache.pool_len();
4038
4039 assert_eq!(compile_to_binary(&shared, &one_cell), private);
4040 assert_eq!(shared.cache.pool_len(), warmed);
4041 }
4042
4043 #[test]
4046 fn clearing_a_shared_cache_releases_templates_and_recompiles_identically() {
4047 let config = CompileConfig::new(3);
4048 let graph = GalleryItem::CNOT
4049 .build()
4050 .flatten()
4051 .expect("gallery graph expands");
4052 let cache = crate::SharedCompileCache::new();
4053
4054 let warm = CompileContext::with_shared_cache(config, &cache);
4055 let expected = compile_to_binary(&warm, &graph);
4056 assert!(warm.cache.pool_len() > 0, "the warm shard holds templates");
4057
4058 cache.clear();
4059
4060 let cold = CompileContext::with_shared_cache(config, &cache);
4063 assert_eq!(cold.cache.pool_len(), 0);
4064 assert_eq!(compile_to_binary(&cold, &graph), expected);
4065 }
4066
4067 #[test]
4070 fn concurrent_shared_cache_compiles_match_sequential_output() {
4071 let config = CompileConfig::new(3);
4072 let graphs: Vec<BlockGraph> = SHARED_CACHE_CASES
4073 .iter()
4074 .map(|item| item.build().flatten().expect("gallery graph expands"))
4075 .collect();
4076 let sequential: Vec<Vec<u8>> = graphs
4077 .iter()
4078 .map(|graph| compile_to_binary(&CompileContext::new(config), graph))
4079 .collect();
4080
4081 let cache = crate::SharedCompileCache::new();
4082 let concurrent: Vec<Vec<u8>> = std::thread::scope(|scope| {
4083 let handles: Vec<_> = graphs
4084 .iter()
4085 .map(|graph| {
4086 let cache = &cache;
4087 scope.spawn(move || {
4088 let ctx = CompileContext::with_shared_cache(config, &cache.clone());
4089 compile_to_binary(&ctx, graph);
4092 compile_to_binary(&ctx, graph)
4093 })
4094 })
4095 .collect();
4096 handles
4097 .into_iter()
4098 .map(|handle| handle.join().expect("compile thread does not panic"))
4099 .collect()
4100 });
4101
4102 assert_eq!(concurrent, sequential);
4103 }
4104
4105 #[test]
4106 fn compile_graph_lowers_isolated_diagonal_walking_block() {
4107 let mut graph = BlockGraph::new();
4108 graph.add_block(bloq_graph::Block::new(
4109 ivec3(0, 0, 0),
4110 BlockKind::Walking(
4111 WalkingKind::new(WalkingBoundaryKind::ZXZ, glam::ivec2(1, 1)).unwrap(),
4112 ),
4113 ));
4114 let ctx = CompileContext::new(CompileConfig::new(3));
4115
4116 let artifacts = ctx.compile(&graph).unwrap();
4117
4118 assert_eq!(artifacts.bloq.quantum_node_count(), 1);
4119 }
4120
4121 #[test]
4122 fn compile_graph_lowers_isolated_axial_walking_block() {
4123 let mut graph = BlockGraph::new();
4124 graph.add_block(bloq_graph::Block::new(
4125 ivec3(0, 0, 0),
4126 BlockKind::Walking(
4127 WalkingKind::new(WalkingBoundaryKind::ZXZ, glam::ivec2(1, 0)).unwrap(),
4128 ),
4129 ));
4130 let ctx = CompileContext::new(CompileConfig::new(3));
4131
4132 let artifacts = ctx.compile(&graph).unwrap();
4133
4134 assert_eq!(artifacts.bloq.quantum_node_count(), 1);
4135 assert_eq!(
4136 source_timeline(&artifacts.bloq, IVec3::ZERO),
4137 Some(&bloq_ir::QuantumTimeline {
4138 layer_round_ends: vec![4, 8],
4139 })
4140 );
4141 }
4142
4143 #[test]
4144 fn compile_graph_sets_patch_rotation_timeline() {
4145 let mut graph = BlockGraph::new();
4146 let kind = PatchRotationKind::new(Basis::X, glam::IVec2::X).expect("valid rotation");
4147 let start = IVec3::Z;
4148 graph.add_block(Block::new(IVec3::ZERO, BlockKind::Port));
4149 graph.add_block(Block::new(start, BlockKind::PatchRotation(kind)));
4150 graph.add_block(Block::new(ivec3(1, 0, 3), BlockKind::Port));
4151 graph.add_pipe(Pipe::new(start, Direction::ZMINUS));
4152 graph.add_pipe(Pipe::new(kind.end_position(start), Direction::ZPLUS));
4153
4154 let program = compile(&graph, 3).expect("patch rotation compiles");
4155 assert_eq!(
4156 source_timeline(&program, start),
4157 Some(&bloq_ir::QuantumTimeline {
4158 layer_round_ends: vec![3, 6],
4159 })
4160 );
4161 }
4162
4163 #[test]
4164 fn compile_graph_lowers_ghz_slide_then_glide_gallery_asset() {
4165 let graph = GalleryItem::GHZSlideThenGlide
4166 .build()
4167 .flatten()
4168 .expect("gallery graph expands");
4169 let ctx = CompileContext::new(CompileConfig::new(3));
4170 let variants = graph
4171 .fill_ports_auto()
4172 .expect("GHZ slide-then-glide ports fill");
4173 assert!(!variants.is_empty());
4174
4175 for (variant, (filled_graph, _)) in variants.iter().enumerate() {
4176 let artifacts = ctx.compile(filled_graph).unwrap_or_else(|error| {
4177 panic!("GHZ slide-then-glide fill variant {variant} compiles: {error}")
4178 });
4179
4180 artifacts
4181 .bloq
4182 .validate()
4183 .expect("GHZ slide-then-glide template instance refs validate");
4184 }
4185 }
4186
4187 #[test]
4190 fn one_context_compiles_concurrently_from_shared_borrows() {
4191 let graphs: Vec<BlockGraph> = SHARED_CACHE_CASES
4192 .iter()
4193 .map(|item| item.build().flatten().expect("gallery graph expands"))
4194 .collect();
4195 let expected: Vec<Vec<u8>> = graphs
4196 .iter()
4197 .map(|graph| compile_to_binary(&CompileContext::new(CompileConfig::default()), graph))
4198 .collect();
4199
4200 let ctx = CompileContext::new(CompileConfig::default());
4201 let concurrent: Vec<Vec<u8>> = std::thread::scope(|scope| {
4202 let handles: Vec<_> = graphs
4203 .iter()
4204 .map(|graph| {
4205 let ctx = &ctx;
4206 scope.spawn(move || compile_to_binary(ctx, graph))
4207 })
4208 .collect();
4209 handles
4210 .into_iter()
4211 .map(|handle| handle.join().expect("compile thread does not panic"))
4212 .collect()
4213 });
4214
4215 assert_eq!(concurrent, expected);
4216 }
4217
4218 #[test]
4219 fn one_shot_compile_matches_the_context_path() {
4220 let graph = GalleryItem::CNOT
4221 .build()
4222 .flatten()
4223 .expect("gallery graph expands");
4224 let expected = CompileContext::new(CompileConfig::new(3))
4225 .compile(&graph)
4226 .expect("context compiles")
4227 .bloq;
4228
4229 assert_eq!(
4230 compile(&graph, 3).expect("one-shot compiles").to_binary(),
4231 expected.to_binary()
4232 );
4233 assert_eq!(
4234 compile_with(&graph, CompileConfig::default())
4235 .expect("configured one-shot compiles")
4236 .bloq
4237 .to_binary(),
4238 expected.to_binary()
4239 );
4240 }
4241
4242 #[test]
4245 fn one_shot_compile_rejects_an_even_distance() {
4246 assert!(matches!(
4247 compile(
4248 &GalleryItem::CNOT
4249 .build()
4250 .flatten()
4251 .expect("gallery graph expands"),
4252 4
4253 ),
4254 Err(CompileError::InvalidDistance(4))
4255 ));
4256 }
4257
4258 #[test]
4259 fn temporal_hadamard_from_port_uses_the_pipe_basis() {
4260 let mut graph = BlockGraph::new();
4261 graph.add_block(Block::new(IVec3::ZERO, BlockKind::Port));
4262 graph.add_block(Block::new(IVec3::Z, BlockKind::Cube(CubeKind::ZXZ)));
4263 graph.add_pipe(Pipe::new(IVec3::ZERO, Direction::ZPLUS).with_hadamard());
4264
4265 compile(&graph, 3)
4266 .expect("Port -> H -> ZXZ compiles")
4267 .validate()
4268 .expect("compiled Bloq validates");
4269 }
4270
4271 #[test]
4272 fn temporal_hadamard_from_tall_cube_uses_its_top_endpoint() {
4273 let mut graph = BlockGraph::new();
4274 graph.add_block(
4275 Block::new(IVec3::ZERO, BlockKind::Cube(CubeKind::ZXZ))
4276 .with_height("2d".parse().expect("valid height"))
4277 .expect("cube accepts height"),
4278 );
4279 graph.add_block(Block::new(IVec3::new(0, 0, 2), BlockKind::Port));
4280 graph.add_pipe(Pipe::new(IVec3::Z, Direction::ZPLUS).with_hadamard());
4281
4282 compile(&graph, 3)
4283 .expect("tall ZXZ -> H -> Port compiles")
4284 .validate()
4285 .expect("compiled Bloq validates");
4286 }
4287
4288 #[test]
4289 fn compiled_metadata_round_trips_through_the_program() {
4290 let bloq = compile(
4291 &GalleryItem::CNOT
4292 .build()
4293 .flatten()
4294 .expect("gallery graph expands"),
4295 5,
4296 )
4297 .expect("compiles");
4298
4299 assert_eq!(compiled_distance(&bloq), Some(5));
4300 assert_eq!(compiled_distance(&Bloq::default()), None);
4301 }
4302
4303 fn cached_module_definitions(
4304 context: &CompileContext,
4305 program: &BlockGraph,
4306 ) -> std::collections::HashMap<String, Arc<PreparedDefinitionObject>> {
4307 let orientations = program.definition_orientations();
4308 program
4309 .module_definition_cache_keys()
4310 .into_iter()
4311 .map(|(name, implementation)| {
4312 let object = context
4313 .cache
4314 .definition_object(&DefinitionObjectCacheKey {
4315 implementation,
4316 orientations: orientations[&name].clone(),
4317 })
4318 .expect("compiled definition is cached");
4319 (name, object)
4320 })
4321 .collect()
4322 }
4323
4324 #[test]
4325 fn shared_cache_reuses_the_compiled_module_object() {
4326 let cache = crate::SharedCompileCache::new();
4327 let config = CompileConfig::default();
4328 let first = CompileContext::with_shared_cache(config, &cache)
4329 .compile_object(&GalleryItem::PhaseGradientK4.build())
4330 .unwrap();
4331 let second = CompileContext::with_shared_cache(config, &cache)
4332 .compile_object(&GalleryItem::PhaseGradientK4.build())
4333 .unwrap();
4334
4335 assert!(Arc::ptr_eq(&first.artifacts, &second.artifacts));
4336 }
4337
4338 #[test]
4339 fn module_link_reports_a_missing_physical_variant() {
4340 let mut graph = BlockGraph::new();
4341 graph.add_block(Block::new(IVec3::ZERO, BlockKind::Cube(CubeKind::ZXZ)));
4342 let definitions = [Arc::new(PreparedDefinitionObject {
4343 name: "Leaf".into(),
4344 dependencies: Vec::new(),
4345 variants: crate::FxMap::default(),
4346 })];
4347 let sites = [PreparedSiteRelocation {
4348 position: IVec3::ZERO,
4349 definition: 0,
4350 local_position: IVec3::ZERO,
4351 }];
4352
4353 assert!(matches!(
4354 select_module_link_templates_with_schedule(
4355 CompileConfig::default(),
4356 &graph,
4357 &SpatialPortExpansionMap::default(),
4358 &definitions,
4359 &sites,
4360 &crate::signature::join_component_layer_schedules(&graph).unwrap(),
4361 &crate::FxMap::default(),
4362 ),
4363 Err(CompileError::MissingModulePhysicalVariant { .. })
4364 ));
4365 }
4366
4367 #[test]
4368 fn clearing_shared_cache_evicts_objects_from_live_contexts() {
4369 let cache = crate::SharedCompileCache::new();
4370 let context = CompileContext::with_shared_cache(CompileConfig::default(), &cache);
4371 let program = GalleryItem::PhaseGradientK4.build();
4372 let first = context.compile_object(&program).unwrap();
4373
4374 cache.clear();
4375 let second = context.compile_object(&program).unwrap();
4376
4377 assert!(!Arc::ptr_eq(&first.artifacts, &second.artifacts));
4378 }
4379
4380 #[test]
4381 fn flat_oracle_artifacts_do_not_replace_hierarchical_objects() {
4382 let flat = GalleryItem::CNOT.build().flatten().unwrap();
4383 let structured = flat.clone().with_inferred_interface().unwrap();
4384 assert_eq!(flat.to_blog_text(), structured.to_blog_text());
4385 let context = CompileContext::new(CompileConfig::default());
4386 let oracle = context.compile_object(&flat).unwrap();
4387 let hierarchy = context.compile_object(&structured).unwrap();
4388 assert!(!Arc::ptr_eq(&oracle.artifacts, &hierarchy.artifacts));
4389 assert!(!cached_module_definitions(&context, &structured).is_empty());
4390 }
4391
4392 #[test]
4393 fn unified_compile_revalidates_edited_hierarchy_before_cache_reuse() {
4394 let mut graph = BlockGraph::from_text(
4395 "BLOG 1.0\nmodule Leaf {\n0: ZXZ [0,0,0]\n}\n\
4396 module main {\nchild: Leaf @ [0,0,0]\n}\n",
4397 )
4398 .unwrap();
4399 let context = CompileContext::new(CompileConfig::default());
4400 context.compile(&graph).unwrap();
4401 context
4402 .summarize(&graph, ModuleCertificationLimits::DEFAULT)
4403 .unwrap();
4404 graph.instances[0].definition = "Missing".to_string();
4405 let error = context.compile(&graph).unwrap_err();
4406 assert!(matches!(error, CompileError::Module(_)), "{error:?}");
4407 assert!(error.to_string().contains("Missing"), "{error}");
4408 let summary_error = context
4409 .summarize(&graph, ModuleCertificationLimits::DEFAULT)
4410 .unwrap_err();
4411 assert!(
4412 matches!(summary_error, ModuleCertificationError::Graph { .. }),
4413 "{summary_error:?}"
4414 );
4415 assert!(
4416 summary_error.to_string().contains("Missing"),
4417 "{summary_error}"
4418 );
4419
4420 graph.instances[0].definition = BlockGraph::ENTRY_MODULE.to_string();
4421 let summary_error = context
4422 .summarize(&graph, ModuleCertificationLimits::DEFAULT)
4423 .unwrap_err();
4424 assert!(
4425 matches!(summary_error, ModuleCertificationError::Graph { .. }),
4426 "{summary_error:?}"
4427 );
4428 assert!(
4429 summary_error.to_string().contains("cycle"),
4430 "{summary_error}"
4431 );
4432 }
4433
4434 #[test]
4435 fn unified_compile_checks_interfaces_added_to_a_plain_graph() {
4436 let mut graph = single_cube_runtime_graph();
4437 graph.interface.quantum_ports.push(bloq_graph::QuantumPort {
4438 name: "missing_port".to_string(),
4439 position: IVec3::X,
4440 direction: bloq_graph::PortDirection::Input,
4441 resource_type: "data".to_string(),
4442 });
4443 assert!(graph.has_module_structure());
4444 let error = CompileContext::new(CompileConfig::default())
4445 .compile(&graph)
4446 .unwrap_err();
4447 assert!(matches!(error, CompileError::Module(_)), "{error:?}");
4448 assert!(error.to_string().contains("missing_port"), "{error}");
4449 }
4450
4451 #[test]
4452 fn module_assembly_retains_cache_hits_across_a_clear() {
4453 let program = BlockGraph::from_text(
4454 "BLOG 1.0\nmodule Leaf {\n0: ZXZ [0,0,0]\n}\n\
4455 module main {\nleaf: Leaf @ [0,0,0]\n}\n",
4456 )
4457 .unwrap();
4458 let cache = crate::SharedCompileCache::new();
4459 let context = CompileContext::with_shared_cache(CompileConfig::default(), &cache);
4460 context.compile_object(&program).unwrap();
4461 let mut retained = cached_module_definitions(&context, &program);
4462 let root = retained.remove(BlockGraph::ENTRY_MODULE).unwrap();
4463 let parts = std::collections::HashMap::from([(root.name.clone(), root.variants.clone())]);
4464 let mut names = program
4465 .module_definition_cache_keys()
4466 .into_keys()
4467 .collect::<Vec<_>>();
4468 names.sort_unstable();
4469
4470 cache.clear();
4473 let definitions = build_definition_objects(
4474 &context.cache,
4475 &names,
4476 parts,
4477 retained,
4478 &program.module_definition_cache_keys(),
4479 &program.definition_orientations(),
4480 &program,
4481 );
4482
4483 let rebuilt_root = definitions
4484 .iter()
4485 .find(|definition| definition.name == "main")
4486 .unwrap();
4487 assert!(Arc::ptr_eq(
4488 &rebuilt_root.dependencies[0],
4489 &root.dependencies[0]
4490 ));
4491 }
4492
4493 #[test]
4494 fn serial_and_parallel_module_builds_link_identically() {
4495 let continuing = BlockGraph::from_text(
4496 "BLOG 1.0
4497module main {
4498 0: ZXZ [0,0,0]
4499 1: ZXZ [0,0,2]
4500 9: ZXZ [2,0,0]
4501 branch b {
4502 false {
4503 2: XZX [0,0,1]
4504 0 -H> +Z
4505 [0,0,1] -H> +Z
4506 }
4507 true {
4508 3: ZXZ [0,0,1]
4509 0 -> +Z
4510 [0,0,1] -> +Z
4511 }
4512 }
4513 m = measure 9
4514 resolve b if m
4515}
4516",
4517 )
4518 .unwrap();
4519 for (label, program) in [
4520 ("phase gradient", GalleryItem::PhaseGradientK4.build()),
4521 ("CCZ", GalleryItem::CCZGateTeleport.build()),
4522 ("continuing", continuing),
4523 ] {
4524 let serial_context = CompileContext::new(CompileConfig::default());
4525 let serial = serial_context
4526 .compile_object_with_jobs(&program, NonZeroUsize::MIN)
4527 .and_then(|object| serial_context.link_object(&object))
4528 .unwrap();
4529 let parallel_context = CompileContext::new(CompileConfig::default());
4530 let parallel = parallel_context
4531 .compile_object_with_jobs(&program, NonZeroUsize::new(4).expect("four is nonzero"))
4532 .and_then(|object| parallel_context.link_object(&object))
4533 .unwrap();
4534
4535 assert_eq!(
4536 serial.bloq.to_binary(),
4537 parallel.bloq.to_binary(),
4538 "{label}"
4539 );
4540 }
4541 }
4542
4543 #[test]
4544 fn definition_objects_survive_a_different_root_archive_key() {
4545 let cache = crate::SharedCompileCache::new();
4546 let config = CompileConfig::default();
4547 let program = BlockGraph::from_text(
4548 "BLOG 1.0\nmodule Leaf {\n0: ZXZ [0,0,0]\n}\n\
4549 module main {\na: Leaf @ [0,0,0]\nb: Leaf @ [2,0,0]\n}\n",
4550 )
4551 .unwrap();
4552 let first_context = CompileContext::with_shared_cache(config, &cache);
4553 let first = first_context.compile_object(&program).unwrap();
4554 let first_definitions = cached_module_definitions(&first_context, &program);
4555
4556 let mut modules = program
4557 .modules()
4558 .map(BlockGraph::clone_local_definition)
4559 .collect::<Vec<_>>();
4560 let mut unused = BlockGraph::from_text(include_str!(
4561 "../../docs/fixtures/conditional_cz_strip.blog"
4562 ))
4563 .unwrap()
4564 .clone_local_definition();
4565 unused.name = "Unused".into();
4566 modules.push(unused);
4567 let with_unused = bloq_graph::BlockGraph::from_definitions(modules)
4568 .expect("unused definition keeps the root valid");
4569 let second_context = CompileContext::with_shared_cache(config, &cache);
4570 let second = second_context.compile_object(&with_unused).unwrap();
4571 let second_definitions = cached_module_definitions(&second_context, &with_unused);
4572
4573 assert!(!Arc::ptr_eq(&first.artifacts, &second.artifacts));
4574 assert_eq!(first_definitions.len(), second_definitions.len());
4575 for (name, left) in first_definitions {
4576 assert!(
4577 Arc::ptr_eq(&left, &second_definitions[&name]),
4578 "{name} was rebuilt"
4579 );
4580 }
4581 }
4582
4583 #[test]
4584 fn spatial_module_seam_promotes_both_sides_before_template_selection() {
4585 let program = BlockGraph::from_text(
4586 "BLOG 1.0\n\n\
4587 module A {\n\
4588 out q: data = 1\n\
4589 0: XXZ [0,0,0]\n\
4590 1: Port [1,0,0] role=output <q>\n\
4591 0 -> +X\n\
4592 }\n\n\
4593 module B {\n\
4594 in q: data = 0\n\
4595 0: Port [-1,0,0] role=input <q>\n\
4596 1: ZXZ [0,0,0]\n\
4597 0 -> +X\n\
4598 }\n\n\
4599 module main {\n\
4600 a: A @ [0,0,0]\n\
4601 b: B @ [1,0,0]\n\
4602 a.q -> b.q\n\
4603 }\n",
4604 )
4605 .unwrap();
4606 let context = CompileContext::new(CompileConfig::default());
4607 let linked = context.compile(&program).unwrap();
4608 let graph = program.flatten().unwrap().fix_shadowed_faces();
4609 let signatures = select_link_signatures(
4610 context.config,
4611 &graph,
4612 &SpatialPortExpansionMap::default(),
4613 &context.flat_signature_builds,
4614 )
4615 .unwrap();
4616
4617 for pos in [IVec3::ZERO, IVec3::X] {
4618 let signature = &signatures[&pos];
4619 assert_eq!(signature.layer_schedule, Some(LayerSchedule::Padded));
4620 }
4621 linked.bloq.validate().unwrap();
4622 let component = linked
4623 .bloq
4624 .nodes()
4625 .find_map(|(_, node)| (node.block_members().len() == 2).then_some(node))
4626 .expect("the spatial seam fuses both module cubes");
4627 assert_eq!(component.expect_quantum().instances.len(), 2);
4628 }
4629
4630 #[test]
4631 fn fat_definition_object_reuses_a_cube_across_compact_and_padded_seams() {
4632 let pair = |neighbor: &str| {
4633 BlockGraph::from_text(&format!(
4634 "BLOG 1.0\n\n\
4635 module A {{\n\
4636 out q: data = 1\n\
4637 0: ZXZ [0,0,0]\n\
4638 1: Port [1,0,0] role=output <q>\n\
4639 0 -> +X\n\
4640 }}\n\n\
4641 module B {{\n\
4642 in q: data = 0\n\
4643 0: Port [-1,0,0] role=input <q>\n\
4644 1: {neighbor} [0,0,0]\n\
4645 0 -> +X\n\
4646 }}\n\n\
4647 module main {{\n\
4648 a: A @ [0,0,0]\n\
4649 b: B @ [1,0,0]\n\
4650 a.q -> b.q\n\
4651 }}\n"
4652 ))
4653 .unwrap()
4654 };
4655 let cache = crate::SharedCompileCache::new();
4656 let config = CompileConfig::default();
4657 let compact_program = pair("ZXZ");
4658 let padded_program = pair("XXZ");
4659 let compact_context = CompileContext::with_shared_cache(config, &cache);
4660 let compact = compact_context.compile(&compact_program).unwrap();
4661 let compact_definition = cached_module_definitions(&compact_context, &compact_program)
4662 .remove("A")
4663 .unwrap();
4664 let padded_context = CompileContext::with_shared_cache(config, &cache);
4665 let padded = padded_context.compile(&padded_program).unwrap();
4666 let padded_definition = cached_module_definitions(&padded_context, &padded_program)
4667 .remove("A")
4668 .unwrap();
4669
4670 assert!(Arc::ptr_eq(&compact_definition, &padded_definition));
4671 let schedules = compact_definition
4672 .variants
4673 .keys()
4674 .filter(|variant| variant.local_position == IVec3::ZERO)
4675 .filter_map(|variant| variant.signature.layer_schedule)
4676 .collect::<crate::FxSet<_>>();
4677 assert!(schedules.contains(&LayerSchedule::Compact));
4678 assert!(schedules.contains(&LayerSchedule::Padded));
4679 let selected = |program: &BlockGraph| {
4680 let context = CompileContext::new(config);
4681 let graph = program.flatten().unwrap().fix_shadowed_faces();
4682 select_link_signatures(
4683 context.config,
4684 &graph,
4685 &SpatialPortExpansionMap::default(),
4686 &context.flat_signature_builds,
4687 )
4688 .unwrap()[&IVec3::ZERO]
4689 .layer_schedule
4690 .unwrap()
4691 };
4692 assert_eq!(selected(&compact_program), LayerSchedule::Compact);
4693 assert_eq!(selected(&padded_program), LayerSchedule::Padded);
4694 compact.bloq.validate().unwrap();
4695 padded.bloq.validate().unwrap();
4696 }
4697
4698 #[test]
4699 fn flat_signature_guard_observes_worker_threads() {
4700 let builds = std::sync::atomic::AtomicUsize::new(0);
4701 let graph = GalleryItem::CNOT
4702 .build()
4703 .flatten()
4704 .expect("gallery graph expands");
4705 std::thread::scope(|scope| {
4706 scope
4707 .spawn(|| {
4708 select_link_signatures(
4709 CompileConfig::default(),
4710 &graph,
4711 &SpatialPortExpansionMap::default(),
4712 &builds,
4713 )
4714 .unwrap();
4715 })
4716 .join()
4717 .unwrap();
4718 });
4719
4720 assert_eq!(builds.load(std::sync::atomic::Ordering::Relaxed), 1);
4721 }
4722
4723 #[test]
4724 fn module_link_never_discovers_flat_signatures() {
4725 let context = CompileContext::new(CompileConfig::default());
4726 context
4727 .compile(&GalleryItem::PhaseGradientK4.build())
4728 .unwrap();
4729 let branch = BlockGraph::from_definitions(vec![BlockGraph::definition(
4730 BlockGraph::ENTRY_MODULE,
4731 structural_branch_graph(),
4732 bloq_graph::ModuleInterface::default(),
4733 Vec::new(),
4734 Vec::new(),
4735 Vec::new(),
4736 )])
4737 .unwrap();
4738 context.compile(&branch).unwrap();
4739
4740 assert_eq!(
4741 context
4742 .flat_signature_builds
4743 .load(std::sync::atomic::Ordering::Relaxed),
4744 0
4745 );
4746 }
4747
4748 #[test]
4752 fn artifacts_carry_the_spatial_hadamard_distance_warning() {
4753 let mut graph = BlockGraph::new();
4754 graph.add_block(Block::new(IVec3::ZERO, BlockKind::Cube(CubeKind::XZX)));
4755 graph.add_block(Block::new(IVec3::X, BlockKind::Cube(CubeKind::XXZ)));
4756 graph.add_pipe(
4757 bloq_graph::Pipe::new(IVec3::ZERO, bloq_graph::Direction::XPLUS).with_hadamard(),
4758 );
4759
4760 let artifacts = compile_with(&graph, CompileConfig::default()).expect("wall compiles");
4761
4762 assert_eq!(
4763 artifacts.warnings,
4764 crate::spatial_hadamard_distance_warning(&graph)
4765 .into_iter()
4766 .collect::<Vec<_>>()
4767 );
4768 assert!(!artifacts.warnings.is_empty());
4769 assert!(
4770 compile_with(
4771 &GalleryItem::CNOT
4772 .build()
4773 .flatten()
4774 .expect("gallery graph expands"),
4775 CompileConfig::default()
4776 )
4777 .expect("CNOT compiles")
4778 .warnings
4779 .is_empty()
4780 );
4781 }
4782}