1use glam::{IVec2, IVec3};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum CompileError {
12 #[error("{0}")]
14 Cancelled(#[from] bloq_graph::ComputationCancelled),
15 #[error(
17 "{0}; {help}",
18 help = bloq_graph::ModuleCertificationLimits::RESOURCE_LIMIT_HELP
19 )]
20 BooleanResource(#[from] bloq_utils::boolean::BooleanResourceError),
21 #[error("invalid distance {0}: must be odd and in 3..=255")]
23 InvalidDistance(u32),
24 #[error("compiled object target {object:?} does not match linker target {linker:?}")]
26 CompiledObjectTargetMismatch {
27 object: Box<crate::CompileConfig>,
29 linker: Box<crate::CompileConfig>,
31 },
32 #[error(
34 "module object '{module}' has no physical ABI variant for local block {local_position:?} linked at {linked_position:?}: {signature}"
35 )]
36 MissingModulePhysicalVariant {
37 module: String,
39 local_position: IVec3,
41 linked_position: IVec3,
43 signature: String,
45 },
46 #[error("Clifford proxy requires {expected} selective pins, got {actual}")]
48 CliffordProxyPinCount {
49 expected: usize,
51 actual: usize,
53 },
54 #[error("Selective({kind}) arm signature mismatch: {reason}")]
56 SelectiveArmSignatureMismatch {
57 kind: bloq_graph::SelectiveKind,
59 reason: String,
61 },
62 #[error("{kind} block has invalid fixed-bulk connectivity {connectivity:?}")]
64 InvalidConnectivity {
65 kind: bloq_graph::BlockKind,
67 connectivity: crate::signature::Connectivity,
69 },
70 #[error("{kind} block requires a temporal cube neighbor to determine its boundary basis")]
72 MissingBoundaryBasis {
73 kind: bloq_graph::BlockKind,
75 },
76 #[error("spatial Port at {port:?} is connected to non-cube block at {neighbor:?}")]
78 SpatialPortNeighborNotCube {
79 port: IVec3,
81 neighbor: IVec3,
83 },
84 #[error("cannot infer the virtual cube kind for spatial Port at {port:?}")]
86 SpatialPortCubeInferenceFailed {
87 port: IVec3,
89 },
90 #[error(
92 "spatial Port at {port:?} borders a tall cube (height={height}); tall spatial-Port substitution is not supported yet"
93 )]
94 TallSpatialPortUnsupported {
95 port: IVec3,
97 height: bloq_graph::CubeHeight,
99 },
100 #[error("cannot allocate an external physical qubit for Multiplex Port at {port:?}")]
102 MultiplexOutputCoordinateUnavailable {
103 port: IVec3,
105 },
106 #[error(
108 "spatial Hadamard pipe endpoint {pos:?} is not a cube; the wall's extended stabilizers \
109 reach into both neighbours' seam-adjacent data columns, which only a cube patch has"
110 )]
111 SpatialHadamardUnsupportedEndpoint {
112 pos: IVec3,
114 },
115 #[error(
117 "spatial component at {pos:?} mixes X- and Y-axis Hadamard walls; fixed-bulk compilation \
118 supports one spatial Hadamard axis per connected component"
119 )]
120 MixedSpatialHadamardUnsupported {
121 pos: IVec3,
123 },
124 #[error(
126 "cube at {pos:?} with height={height} compiles to {rounds} syndrome rounds at distance \
127 {distance}; a cube needs at least 2 (one initialization stage, one measurement stage)"
128 )]
129 CubeHeightTooShort {
130 pos: IVec3,
132 height: bloq_graph::CubeHeight,
134 distance: u32,
136 rounds: i64,
138 },
139 #[error(
141 "block coordinate {block_xy:?} at distance {distance} exceeds the signed 32-bit qubit layout"
142 )]
143 BlockLayoutCoordinateOverflow {
144 block_xy: glam::IVec2,
146 distance: u32,
148 },
149 #[error(
151 "WF-LAYOUT: same-layer Bloq nodes {first:?} and {second:?} own overlapping physical qubit {qubit:?}"
152 )]
153 OverlappingNodeQubit {
154 first: bloq_ir::BloqNodeId,
156 second: bloq_ir::BloqNodeId,
158 qubit: IVec2,
160 },
161 #[error("PatchRotation block construction failed: {reason}")]
163 PatchRotationConstructionFailed {
164 reason: String,
166 },
167 #[error("program node flow composition failed for layer {layer} members {members:?}: {source}")]
169 NodeFlowCompositionFailed {
170 layer: i64,
172 members: Vec<IVec3>,
174 #[source]
176 source: bloq_circuit::FlowError,
177 },
178 #[error("program detector composition failed: {source}")]
180 DetectorFlowCompositionFailed {
181 #[source]
183 source: bloq_circuit::FlowError,
184 },
185 #[error(
187 "loop-boundary residual with a moving operator (non-empty flow start) inside a repeat body is unsupported"
188 )]
189 LoopBoundaryMovingOperatorUnsupported,
190 #[error(
192 "compiler-generated ConditionalPauli requires the commuting-feedback path or parity folding"
193 )]
194 ConditionalPauliFeedbackUnsupported,
195 #[error(
197 "T block at {pos:?} needs a free spatial neighbor cell (-y/+x/+y/-x) for the Steane cultivation spill; all four are occupied or reserved by another T block"
198 )]
199 TBlockNeedsFreeNeighbor {
200 pos: IVec3,
202 },
203 #[error("Walking block construction failed: {0}")]
205 WalkingConstruction(#[source] WalkError),
206 #[error(
208 "observable composition failed: missing gateway entry {local_stabilizer} at {block_pos:?} for observable {observable_index}"
209 )]
210 ObservableMissingGatewayEntry {
211 block_pos: IVec3,
213 observable_index: u32,
215 local_stabilizer: String,
217 },
218 #[error(
220 "observable {observable_index} boundary operator is discontinuous across the temporal seam {lower:?} -> {upper:?}: the lower block's +Z `operator_out` must equal the upper block's -Z `operator_in`"
221 )]
222 ObservableSeamMismatch {
223 observable_index: u32,
225 lower: IVec3,
227 upper: IVec3,
229 },
230 #[error("continuing branch assembly cannot be scheduled: {reason}")]
232 BranchAssembly {
233 reason: String,
235 },
236 #[error(
238 "Clifford proxies do not support structural branches because their pins only resolve selective blocks"
239 )]
240 StructuralBranchCliffordProxyUnsupported,
241 #[error("memory-round padding requires at least one round (U11)")]
243 PaddingRoundsZero,
244 #[error("seam edit failed (U12): {0}")]
246 Edit(#[from] bloq_ir::EditError),
247 #[error("{0}")]
249 BloqValidation(#[from] bloq_ir::BloqValidationError),
250 #[error("{0}")]
252 CoordinateOverflow(#[from] bloq_ir::CoordinateOverflowError),
253 #[error(
255 "rebasing logical-boundary coordinate {coordinate:?} around patch offset {offset:?} exceeds the signed 32-bit lattice"
256 )]
257 LogicalBoundaryRebaseOverflow {
258 coordinate: glam::IVec2,
260 offset: glam::IVec2,
262 },
263 #[error("{0}")]
265 Circuit(#[from] bloq_circuit::CircuitError),
266 #[error("{0}")]
268 RuntimeBasis(#[from] bloq_graph::RuntimeBasisError),
269 #[error("{0}")]
271 SymbolicBasis(#[from] bloq_graph::SymbolicBasisError),
272 #[error("{0}")]
274 OutputCorrection(#[from] bloq_graph::OutputCorrectionError),
275 #[error("{0}")]
277 BlockGraph(#[from] bloq_graph::BlockGraphError),
278 #[error("{0}")]
280 ModuleCertification(#[from] bloq_graph::ModuleCertificationError),
281 #[error("{0}")]
283 Module(#[from] Box<bloq_graph::ModuleError>),
284}
285
286impl From<bloq_graph::ModuleError> for CompileError {
287 fn from(error: bloq_graph::ModuleError) -> Self {
288 Self::Module(Box::new(error))
289 }
290}
291
292pub(crate) fn check_resource(
304 resource: &'static str,
305 observed: usize,
306 limit: usize,
307) -> Result<(), CompileError> {
308 if observed > limit {
309 return Err(bloq_utils::boolean::BooleanResourceError {
310 resource,
311 observed,
312 limit,
313 }
314 .into());
315 }
316 Ok(())
317}
318
319pub(crate) fn add_resource(
321 resource: &'static str,
322 current: usize,
323 additional: usize,
324 limit: usize,
325) -> Result<usize, CompileError> {
326 let total = current.checked_add(additional).ok_or_else(|| {
327 CompileError::BooleanResource(bloq_utils::boolean::BooleanResourceError {
328 resource,
329 observed: usize::MAX,
330 limit,
331 })
332 })?;
333 check_resource(resource, total, limit)?;
334 Ok(total)
335}
336
337impl CompileError {
338 pub(crate) fn from_verification_audit(error: bloq_ir::BloqValidationError) -> Self {
341 use bloq_ir::BloqValidationError;
342 use bloq_ir::lowering::NodeTemplateInstanceMergeError;
343
344 match error {
345 BloqValidationError::BooleanResource(resource)
346 | BloqValidationError::InvalidInstanceMergeStructure {
347 source: NodeTemplateInstanceMergeError::BooleanResource(resource),
348 ..
349 }
350 | BloqValidationError::InvalidTemplateCircuit {
351 source: NodeTemplateInstanceMergeError::BooleanResource(resource),
352 ..
353 } => Self::BooleanResource(resource),
354 error => Self::BloqValidation(error),
355 }
356 }
357
358 pub fn is_resource_limited(&self) -> bool {
361 self.resource_limited_error().is_some()
362 }
363
364 pub fn is_cancelled(&self) -> bool {
367 let mut error: &(dyn std::error::Error + 'static) = self;
368 loop {
369 if error.is::<bloq_graph::ComputationCancelled>() {
370 return true;
371 }
372 let Some(source) = error.source() else {
373 return false;
374 };
375 error = source;
376 }
377 }
378
379 pub fn resource_limit_help(&self) -> Option<&'static str> {
384 let error = self.resource_limited_error()?;
385 (error.is::<bloq_utils::boolean::BooleanResourceError>()
386 || error.is::<bloq_graph::StabilizerError>()
387 || error.is::<bloq_graph::ModuleCertificationError>())
388 .then_some(bloq_graph::ModuleCertificationLimits::RESOURCE_LIMIT_HELP)
389 }
390
391 fn resource_limited_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
392 let mut error: &(dyn std::error::Error + 'static) = self;
393 loop {
394 if error.is::<bloq_utils::boolean::BooleanResourceError>()
395 || matches!(
396 error.downcast_ref::<bloq_graph::StabilizerError>(),
397 Some(bloq_graph::StabilizerError::ResourceLimited { .. })
398 )
399 || matches!(
400 error.downcast_ref::<bloq_graph::ModuleCertificationError>(),
401 Some(bloq_graph::ModuleCertificationError::ResourceLimited { .. })
402 )
403 || error
404 .downcast_ref::<bloq_ir::BloqValidationError>()
405 .is_some_and(bloq_ir::BloqValidationError::is_resource_limited)
406 || error
407 .downcast_ref::<bloq_ir::lowering::NodeTemplateInstanceMergeError>()
408 .is_some_and(
409 bloq_ir::lowering::NodeTemplateInstanceMergeError::is_resource_limited,
410 )
411 || matches!(
412 error.downcast_ref::<bloq_circuit::CircuitError>(),
413 Some(bloq_circuit::CircuitError::FlattenResourceLimit { .. })
414 )
415 {
416 return Some(error);
417 }
418 error = error.source()?;
419 }
420 }
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
430#[error("invalid code distance {0}: must be odd and in 3..=255")]
431pub struct InvalidDistance(pub u32);
432
433impl From<InvalidDistance> for CompileError {
434 fn from(err: InvalidDistance) -> Self {
435 CompileError::InvalidDistance(err.0)
436 }
437}
438
439#[derive(Debug, Clone, Error)]
442#[non_exhaustive]
443pub enum WalkError {
444 #[error("reinclude qubit {qubit} is outside the step frame")]
446 ReincludeOutsideFrame {
447 qubit: glam::IVec2,
449 },
450 #[error("multiple regions reinclude qubit {qubit}")]
452 DuplicateReinclude {
453 qubit: glam::IVec2,
455 },
456 #[error("contracting {basis} region kept unmeasured qubit {qubit}")]
458 ContractingKeptUnmeasuredQubit {
459 basis: bloq_graph::Basis,
461 qubit: glam::IVec2,
463 },
464 #[error("expanding {basis} region hit multiple output measure qubits: {first} and {second}")]
466 ExpandingMultipleOutputMeasures {
467 basis: bloq_graph::Basis,
469 first: glam::IVec2,
471 second: glam::IVec2,
473 },
474 #[error("final {basis} measurement left continuing output {output}")]
476 FinalMeasurementLeftOutput {
477 basis: bloq_graph::Basis,
479 output: bloq_circuit::PauliMap,
481 },
482 #[error("flow references unmeasured qubit {qubit}")]
484 FlowReferencesUnmeasuredQubit {
485 qubit: glam::IVec2,
487 },
488 #[error("final {basis} flow contains non-matching Pauli {pauli} at {coord}")]
490 FinalFlowMismatchedPauli {
491 basis: bloq_graph::Basis,
493 pauli: bloq_graph::Pauli,
495 coord: glam::IVec2,
497 },
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn flow_composition_errors_retain_their_cause() {
506 let source =
507 bloq_circuit::FlowError::CoordinateOverflow(bloq_circuit::CoordinateOverflowError {
508 coordinate: IVec2::MAX,
509 offset: IVec2::ONE,
510 });
511 for error in [
512 CompileError::NodeFlowCompositionFailed {
513 layer: 1,
514 members: vec![],
515 source: source.clone(),
516 },
517 CompileError::DetectorFlowCompositionFailed {
518 source: source.clone(),
519 },
520 ] {
521 assert_eq!(
522 std::error::Error::source(&error)
523 .and_then(|error| error.downcast_ref::<bloq_circuit::FlowError>()),
524 Some(&source),
525 );
526 }
527 }
528}