Skip to main content

bloq/
lib.rs

1//! Compile block graphs for fault-tolerant quantum circuits.
2//!
3//! Workspace crates are available as [`graph`], [`compile`], [`ir`], and other
4//! modules. Import common types and entry points with [`prelude`].
5//! Use [`Result`] for workflows spanning these modules; [`Error`] preserves
6//! their typed failures and supports automatic conversion with `?`.
7//!
8//! The whole pipeline in one call:
9//!
10//! ```
11//! use bloq::prelude::*;
12//!
13//! let stim_text = compile_to_stim(&GalleryItem::CNOT.build(), 3)?;
14//! assert!(stim_text.contains("QUBIT_COORDS"));
15//! # Ok::<(), bloq::Error>(())
16//! ```
17//!
18//! Spelled out, when the intermediate [`Bloq`](bloq_ir::Bloq) is wanted or a
19//! non-default [`CompileConfig`](bloq_compile::CompileConfig) applies:
20//!
21//! ```
22//! use bloq::prelude::*;
23//!
24//! let graph = GalleryItem::CNOT.build();
25//! let bloq = compile(&graph, 3)?;
26//! let stim_text = emit_bloq_stim(&bloq)?;
27//! assert!(stim_text.contains("QUBIT_COORDS"));
28//! # Ok::<(), bloq::Error>(())
29//! ```
30//!
31//! Compiling many related graphs with one configuration can use a
32//! [`CompileContext`](bloq_compile::CompileContext) to reuse their circuit
33//! templates.
34//!
35//! Default features are empty, so library use pulls in no CLI dependencies.
36//! The separate `bloq-cli` package provides the `bloq` command-line binary
37//! and the shared runner used by Python. `vm` exposes physical execution. `gltf` and
38//! `verify` forward to
39//! `bloq_graph/gltf` and `bloq_stim/verify` respectively. `graph-verify` exposes
40//! graph-level logical verification without requiring the native Stim backend.
41
42pub use bloq_circuit as circuit;
43pub use bloq_compile as compile;
44pub use bloq_graph as graph;
45pub use bloq_ir as ir;
46pub use bloq_stim as stim;
47pub use bloq_utils as utils;
48#[cfg(feature = "vm")]
49pub use bloq_vm as vm;
50
51mod error;
52pub use error::{Error, Result};
53
54use bloq_graph::BlockGraph;
55
56/// Compile `graph` at `distance` and emit it as Stim circuit text.
57///
58/// Uses a fresh template cache. Equivalent to
59/// [`bloq_compile::compile`] followed by [`bloq_stim::emit_bloq_stim`].
60///
61/// # Errors
62///
63/// Wraps [`compile::CompileError`] if `distance` is invalid or `graph` does not
64/// compile, and [`stim::StimEmissionError`] if the compiled program uses a
65/// node the static Stim backend cannot express (dynamic `RepeatUntilSuccess` or
66/// retry regions or unpinned quantum membership — use `bloq_vm` for dynamic execution).
67///
68/// # Examples
69///
70/// ```
71/// let stim_text = bloq::compile_to_stim(&bloq::graph::GalleryItem::CNOT.build(), 3)?;
72/// assert!(stim_text.contains("QUBIT_COORDS"));
73/// # Ok::<(), bloq::Error>(())
74/// ```
75pub fn compile_to_stim(graph: &BlockGraph, distance: u32) -> Result<String> {
76    let bloq = bloq_compile::compile(graph, distance)?;
77    Ok(bloq_stim::emit_bloq_stim(&bloq)?)
78}
79
80/// Graph authoring, compilation, and Stim emission, for `use bloq::prelude::*`.
81///
82/// Advanced compiler controls live in [`crate::compile`]; circuit construction
83/// and physical IR editing use [`crate::circuit`] and [`crate::ir`], including
84/// their respective preludes.
85///
86/// Load and compile a module hierarchy from disk:
87///
88/// ```no_run
89/// use bloq::prelude::*;
90///
91/// let graph = BlockGraph::load("input.blog")?;
92/// let config = CompileConfig::try_new(5)?;
93/// let artifacts = CompileContext::new(config).compile(&graph)?;
94/// std::fs::write("out.stim", emit_bloq_stim(&artifacts.bloq)?)?;
95/// # Ok::<(), bloq::Error>(())
96/// ```
97pub mod prelude {
98    #[doc(no_inline)]
99    pub use bloq_circuit::NoiseModel;
100    #[doc(no_inline)]
101    pub use bloq_compile::{
102        CompileArtifacts, CompileConfig, CompileContext, CompileError, compile, compile_with,
103    };
104    #[doc(no_inline)]
105    pub use bloq_graph::prelude::*;
106    #[doc(no_inline)]
107    pub use bloq_ir::Bloq;
108    #[doc(no_inline)]
109    pub use bloq_stim::prelude::*;
110
111    #[doc(no_inline)]
112    pub use crate::{Error, Result, compile_to_stim};
113
114    /// Integer coordinates for block graphs and qubit layouts.
115    #[doc(no_inline)]
116    pub use glam::IVec3;
117}
118
119// Compile the user documentation in the existing doctest gate without adding
120// its prose to the facade's published API reference.
121#[cfg(doctest)]
122mod documentation {
123    #[doc = include_str!("../../README.md")]
124    mod readme {}
125    #[doc = include_str!("../../docs/guide.md")]
126    mod guide {}
127    #[doc = include_str!("../../docs/releasing.md")]
128    mod releasing {}
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    fn module_stim(source: &str, distance: u32) -> Result<String> {
136        use crate::prelude::*;
137
138        let source = BlockGraph::from_text(source)?;
139        let config = CompileConfig::try_new(distance)?;
140        let artifacts = CompileContext::new(config).compile(&source)?;
141        let program = Bloq::from_binary(&artifacts.bloq.to_binary())?;
142        let program = Bloq::from_text(&program.to_text())?;
143        program.validate()?;
144        Ok(emit_bloq_stim(&program)?)
145    }
146
147    #[test]
148    fn facade_result_composes_modules_compilation_and_codecs() -> Result {
149        use std::error::Error as _;
150
151        let source = graph::GalleryItem::CNOT.build().to_blog_text();
152        assert!(module_stim(&source, 3)?.contains("QUBIT_COORDS"));
153        let error = module_stim("invalid BLOG", 3).unwrap_err();
154        assert!(error.is::<graph::BlockGraphError>());
155        assert!(error.source().unwrap().is::<graph::BlockGraphError>());
156        assert!(matches!(
157            module_stim(&source, 4).unwrap_err().downcast_ref(),
158            Some(compile::InvalidDistance(4))
159        ));
160        Ok(())
161    }
162
163    #[test]
164    fn facade_error_retains_typed_sources_and_resource_failures() {
165        use std::error::Error as _;
166
167        fn assert_thread_safe<E: std::error::Error + Send + Sync + 'static>() {}
168        assert_thread_safe::<Error>();
169
170        let resource = utils::boolean::BooleanResourceError {
171            resource: "Boolean steps",
172            observed: 2,
173            limit: 1,
174        };
175        let source = compile::CompileError::from(resource);
176        let message = source.to_string();
177        let error = Error::from(source);
178        assert_eq!(error.to_string(), message);
179        let source = error
180            .source()
181            .unwrap()
182            .downcast_ref::<compile::CompileError>()
183            .unwrap();
184        assert!(source.is_resource_limited());
185        assert!(
186            matches!(source, compile::CompileError::BooleanResource(actual) if *actual == resource)
187        );
188        assert!(error.is::<compile::CompileError>());
189        assert!(error.downcast_ref::<graph::ParseError>().is_none());
190
191        let error = Error::from(std::io::Error::other("output write failed"));
192        assert_eq!(error.to_string(), "output write failed");
193        assert!(error.source().unwrap().is::<std::io::Error>());
194    }
195
196    #[cfg(feature = "vm")]
197    #[test]
198    fn facade_result_drives_vm_and_serializes_artifacts() -> Result {
199        let program = compile::compile(&graph::GalleryItem::CNOT.build(), 3)?;
200        let config = vm::LoweringConfig::default();
201        let program = vm::lower(&program, &config)?;
202        let result = program.run(config.runtime_config(42))?;
203        assert!(!result.artifact.discarded);
204        assert!(!result.artifact.to_json()?.is_empty());
205        Ok(())
206    }
207
208    /// The two failure domains stay distinguishable: a bad distance is a
209    /// compile error, and a dynamic program is an emission error.
210    #[test]
211    fn compile_to_stim_reports_which_stage_failed() {
212        assert!(matches!(
213            compile_to_stim(&graph::GalleryItem::CNOT.build(), 4)
214                .unwrap_err()
215                .downcast_ref(),
216            Some(compile::CompileError::InvalidDistance(4))
217        ));
218        assert!(
219            compile_to_stim(&graph::GalleryItem::T.build(), 3)
220                .unwrap_err()
221                .is::<stim::StimEmissionError>()
222        );
223    }
224}