1use std::sync::{Arc, OnceLock};
2
3use bloq_circuit::{DetectorCoords, DetectorParity, DetectorTerm};
4use glam::IVec2;
5
6use super::{
7 Bloq, DetectorBundleId, InstanceMeasurement, NodeDetector, NodeDetectorParity, QuantumNode,
8 TemplateId, TemplateInstanceId,
9};
10
11#[derive(
13 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
14)]
15pub struct BundleMeasurement {
16 pub owner: u32,
18 pub measurement: u32,
20}
21
22#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
24pub struct BundleDetector {
25 pub parity: DetectorParity<BundleMeasurement>,
27 pub coords: Option<DetectorCoords>,
29}
30
31#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct DetectorBundle {
34 owner_templates: Vec<TemplateId>,
35 detectors: Vec<BundleDetector>,
36 #[serde(skip)]
37 used_owners: OnceLock<Result<Vec<u32>, DetectorBundleError>>,
38}
39
40impl DetectorBundle {
41 pub fn new(owner_templates: Vec<TemplateId>, detectors: Vec<BundleDetector>) -> Self {
43 Self {
44 owner_templates,
45 detectors,
46 used_owners: OnceLock::new(),
47 }
48 }
49
50 pub fn owner_templates(&self) -> &[TemplateId] {
52 &self.owner_templates
53 }
54
55 pub fn detectors(&self) -> &[BundleDetector] {
57 &self.detectors
58 }
59
60 pub fn used_owners(&self) -> Result<&[u32], DetectorBundleError> {
67 self.used_owners
68 .get_or_init(|| {
69 if self.owner_templates.len() > u32::MAX as usize {
70 return Err(DetectorBundleError::CountOverflow);
71 }
72 let mut used = vec![false; self.owner_templates.len()];
73 for detector in &self.detectors {
74 for term in detector.parity.terms() {
75 if let DetectorTerm::Measurement(measurement) = term {
76 if measurement.owner as usize >= self.owner_templates.len() {
77 return Err(DetectorBundleError::InvalidLocalOwner {
78 owner: measurement.owner,
79 owners: self.owner_templates.len(),
80 });
81 }
82 used[measurement.owner as usize] = true;
83 }
84 }
85 }
86 Ok(used
87 .into_iter()
88 .enumerate()
89 .filter_map(|(index, present)| present.then_some(index as u32))
90 .collect())
91 })
92 .as_ref()
93 .map(Vec::as_slice)
94 .map_err(Clone::clone)
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
100pub struct DetectorBundleUse {
101 pub bundle: DetectorBundleId,
103 pub instances: Vec<TemplateInstanceId>,
105 pub offset: IVec2,
107}
108
109#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
111pub struct DetectorBundlePool {
112 #[serde(deserialize_with = "super::id::deserialize_pool")]
113 bundles: Vec<Arc<DetectorBundle>>,
114}
115
116fn check_table_count(count: usize) -> Result<(), DetectorBundleError> {
117 u32::try_from(count)
118 .map(|_| ())
119 .map_err(|_| DetectorBundleError::CountOverflow)
120}
121
122impl QuantumNode {
123 pub(crate) fn check_side_table_counts(&self) -> Result<(), DetectorBundleError> {
124 for count in [
125 self.detectors.len(),
126 self.detector_bundles.len(),
127 self.restarts.len(),
128 ] {
129 check_table_count(count)?;
130 }
131 Ok(())
132 }
133}
134
135impl DetectorBundlePool {
136 pub fn new() -> Self {
138 Self::default()
139 }
140
141 pub fn insert(&mut self, bundle: DetectorBundle) -> DetectorBundleId {
147 self.insert_shared(Arc::new(bundle))
148 }
149
150 pub fn insert_shared(&mut self, bundle: Arc<DetectorBundle>) -> DetectorBundleId {
156 let id = DetectorBundleId(super::id::pool_index(self.bundles.len()));
157 self.bundles.push(bundle);
158 id
159 }
160
161 pub fn get(&self, id: DetectorBundleId) -> Option<&DetectorBundle> {
163 self.bundles.get(id.0 as usize).map(Arc::as_ref)
164 }
165
166 pub fn iter(&self) -> impl Iterator<Item = (DetectorBundleId, &DetectorBundle)> {
168 self.iter_shared().map(|(id, bundle)| (id, bundle.as_ref()))
169 }
170
171 pub fn iter_shared(&self) -> impl Iterator<Item = (DetectorBundleId, &Arc<DetectorBundle>)> {
173 self.bundles
174 .iter()
175 .enumerate()
176 .map(|(index, bundle)| (DetectorBundleId(super::id::pool_index(index)), bundle))
177 }
178
179 pub fn len(&self) -> usize {
181 self.bundles.len()
182 }
183
184 pub fn is_empty(&self) -> bool {
186 self.bundles.is_empty()
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
192#[non_exhaustive]
193pub enum DetectorBundleError {
194 #[error("unknown detector bundle {0:?}")]
196 UnknownBundle(DetectorBundleId),
197 #[error("detector bundle {bundle:?} binds {actual} instances; expected {expected}")]
199 BindingArity {
200 bundle: DetectorBundleId,
202 expected: usize,
204 actual: usize,
206 },
207 #[error("detector bundle owner slot {owner} exceeds {owners} owners")]
209 InvalidLocalOwner {
210 owner: u32,
212 owners: usize,
214 },
215 #[error("detector bundle {bundle:?} owner {owner} binds unknown instance {instance:?}")]
217 UnknownBoundInstance {
218 bundle: DetectorBundleId,
220 owner: u32,
222 instance: TemplateInstanceId,
224 },
225 #[error(
227 "detector bundle {bundle:?} owner {owner} expects {expected:?}, but {instance:?} uses {actual:?}"
228 )]
229 OwnerTemplateMismatch {
230 bundle: DetectorBundleId,
232 owner: u32,
234 instance: TemplateInstanceId,
236 expected: TemplateId,
238 actual: TemplateId,
240 },
241 #[error("detector count or address overflows")]
243 CountOverflow,
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
248pub enum NodeDetectorAddress {
249 Inline(u32),
251 Bundle {
253 use_index: u32,
255 row: u32,
257 },
258}
259
260#[derive(Debug, Clone, Copy)]
262pub struct NodeDetectorView<'a> {
263 address: NodeDetectorAddress,
264 source: NodeDetectorSource<'a>,
265 offset: IVec2,
266}
267
268#[derive(Debug, Clone, Copy)]
269enum NodeDetectorSource<'a> {
270 Inline(&'a NodeDetector),
271 Bundle(&'a BundleDetector, &'a [TemplateInstanceId]),
272}
273
274impl NodeDetectorView<'_> {
275 pub fn address(&self) -> NodeDetectorAddress {
277 self.address
278 }
279
280 pub fn terms(&self) -> impl Iterator<Item = DetectorTerm<InstanceMeasurement>> + '_ {
282 let (inline, bundle) = match self.source {
283 NodeDetectorSource::Inline(detector) => (Some(detector), None),
284 NodeDetectorSource::Bundle(detector, instances) => (None, Some((detector, instances))),
285 };
286 inline
287 .map(|detector| detector.parity.terms().iter().copied())
288 .into_iter()
289 .flatten()
290 .chain(
291 bundle
292 .map(|(detector, instances)| {
293 detector.parity.terms().iter().map(move |term| match *term {
294 DetectorTerm::Measurement(measurement) => {
295 DetectorTerm::Measurement(InstanceMeasurement {
296 instance: instances[measurement.owner as usize],
297 measurement: measurement.measurement,
298 })
299 }
300 DetectorTerm::LoopState(state) => DetectorTerm::LoopState(state),
301 })
302 })
303 .into_iter()
304 .flatten(),
305 )
306 }
307
308 pub fn measurements(&self) -> impl Iterator<Item = InstanceMeasurement> + '_ {
310 self.terms().filter_map(|term| match term {
311 DetectorTerm::Measurement(measurement) => Some(measurement),
312 DetectorTerm::LoopState(_) => None,
313 })
314 }
315
316 pub fn sign(&self) -> bool {
318 match self.source {
319 NodeDetectorSource::Inline(detector) => detector.parity.sign(),
320 NodeDetectorSource::Bundle(detector, _) => detector.parity.sign(),
321 }
322 }
323
324 pub fn coords(&self) -> Option<impl Iterator<Item = f64> + '_> {
326 let coords = match self.source {
327 NodeDetectorSource::Inline(detector) => detector.coords.as_ref(),
328 NodeDetectorSource::Bundle(detector, _) => detector.coords.as_ref(),
329 }?;
330 Some(coords.iter().enumerate().map(move |(index, &coord)| {
331 if matches!(self.source, NodeDetectorSource::Inline(_)) {
332 return coord;
333 }
334 match index {
335 0 => coord + self.offset.x as f64,
336 1 => coord + self.offset.y as f64,
337 _ => coord,
338 }
339 }))
340 }
341
342 pub fn parity(&self) -> NodeDetectorParity {
344 NodeDetectorParity::from_terms(self.terms()).with_sign(self.sign())
345 }
346
347 pub fn to_owned(&self) -> NodeDetector {
349 NodeDetector {
350 parity: self.parity(),
351 coords: self.coords().map(Iterator::collect),
352 }
353 }
354}
355
356#[derive(Debug)]
358pub struct NodeDetectors<'a> {
359 node: &'a QuantumNode,
360 pool: &'a DetectorBundlePool,
361 inline_index: usize,
362 use_index: usize,
363 row_index: usize,
364}
365
366impl<'a> Iterator for NodeDetectors<'a> {
367 type Item = NodeDetectorView<'a>;
368
369 fn next(&mut self) -> Option<Self::Item> {
370 if let Some(detector) = self.node.detectors.get(self.inline_index) {
371 let address = NodeDetectorAddress::Inline(self.inline_index as u32);
372 self.inline_index += 1;
373 return Some(NodeDetectorView {
374 address,
375 source: NodeDetectorSource::Inline(detector),
376 offset: IVec2::ZERO,
377 });
378 }
379 loop {
380 let bundle_use = self.node.detector_bundles.get(self.use_index)?;
381 let bundle = self
382 .pool
383 .get(bundle_use.bundle)
384 .expect("preflight checked bundle");
385 if let Some(detector) = bundle.detectors.get(self.row_index) {
386 let address = NodeDetectorAddress::Bundle {
387 use_index: self.use_index as u32,
388 row: self.row_index as u32,
389 };
390 self.row_index += 1;
391 return Some(NodeDetectorView {
392 address,
393 source: NodeDetectorSource::Bundle(detector, &bundle_use.instances),
394 offset: bundle_use.offset,
395 });
396 }
397 self.use_index += 1;
398 self.row_index = 0;
399 }
400 }
401}
402
403impl Bloq {
404 pub fn node_detector_count(&self, node: &QuantumNode) -> Result<usize, DetectorBundleError> {
411 node.check_side_table_counts()?;
412 let mut count = node.detectors.len();
413 for bundle_use in &node.detector_bundles {
414 let bundle = self
415 .detector_bundles()
416 .get(bundle_use.bundle)
417 .ok_or(DetectorBundleError::UnknownBundle(bundle_use.bundle))?;
418 let expected = bundle.owner_templates.len();
419 let actual = bundle_use.instances.len();
420 if actual != expected {
421 return Err(DetectorBundleError::BindingArity {
422 bundle: bundle_use.bundle,
423 expected,
424 actual,
425 });
426 }
427 bundle.used_owners()?;
428 if bundle.detectors.len() > u32::MAX as usize {
429 return Err(DetectorBundleError::CountOverflow);
430 }
431 count = count
432 .checked_add(bundle.detectors.len())
433 .ok_or(DetectorBundleError::CountOverflow)?;
434 }
435 if self.detector_bundles().len() > u32::MAX as usize {
436 return Err(DetectorBundleError::CountOverflow);
437 }
438 Ok(count)
439 }
440
441 pub fn check_detector_bundle_bindings(
449 &self,
450 node: &QuantumNode,
451 mut lookup: impl FnMut(TemplateInstanceId) -> Option<TemplateId>,
452 ) -> Result<(), DetectorBundleError> {
453 self.node_detector_count(node)?;
454 for bundle_use in &node.detector_bundles {
455 let bundle = self
456 .detector_bundles()
457 .get(bundle_use.bundle)
458 .ok_or(DetectorBundleError::UnknownBundle(bundle_use.bundle))?;
459 for (owner, (&instance, &expected)) in bundle_use
460 .instances
461 .iter()
462 .zip(bundle.owner_templates.iter())
463 .enumerate()
464 {
465 let actual = lookup(instance).ok_or(DetectorBundleError::UnknownBoundInstance {
466 bundle: bundle_use.bundle,
467 owner: owner as u32,
468 instance,
469 })?;
470 if actual != expected {
471 return Err(DetectorBundleError::OwnerTemplateMismatch {
472 bundle: bundle_use.bundle,
473 owner: owner as u32,
474 instance,
475 expected,
476 actual,
477 });
478 }
479 }
480 }
481 Ok(())
482 }
483
484 pub fn node_detectors<'a>(
490 &'a self,
491 node: &'a QuantumNode,
492 ) -> Result<NodeDetectors<'a>, DetectorBundleError> {
493 self.node_detector_count(node)?;
494 Ok(NodeDetectors {
495 node,
496 pool: self.detector_bundles(),
497 inline_index: 0,
498 use_index: 0,
499 row_index: 0,
500 })
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use bloq_circuit::LoopStateId;
507
508 use super::*;
509
510 #[test]
511 fn side_table_counts_fit_guard_index_counters() {
512 assert_eq!(check_table_count(u32::MAX as usize), Ok(()));
513 if let Some(exhausted) = (u32::MAX as usize).checked_add(1) {
514 assert_eq!(
515 check_table_count(exhausted),
516 Err(DetectorBundleError::CountOverflow)
517 );
518 }
519 }
520
521 #[test]
522 fn bound_rows_translate_and_cancel_aliased_measurements() {
523 let mut bloq = Bloq::new();
524 let bundle = bloq.add_detector_bundle(DetectorBundle::new(
525 vec![TemplateId(0), TemplateId(0)],
526 vec![BundleDetector {
527 parity: DetectorParity::from_terms([
528 DetectorTerm::Measurement(BundleMeasurement {
529 owner: 0,
530 measurement: 7,
531 }),
532 DetectorTerm::Measurement(BundleMeasurement {
533 owner: 1,
534 measurement: 7,
535 }),
536 DetectorTerm::LoopState(LoopStateId(2)),
537 ])
538 .with_sign(true),
539 coords: Some(DetectorCoords::from_slice(&[1.0, 2.0, 3.0])),
540 }],
541 ));
542 let node = QuantumNode {
543 detector_bundles: vec![DetectorBundleUse {
544 bundle,
545 instances: vec![TemplateInstanceId(4), TemplateInstanceId(4)],
546 offset: IVec2::new(10, -2),
547 }],
548 ..QuantumNode::default()
549 };
550 assert_eq!(bloq.node_detector_count(&node), Ok(1));
551 let row = bloq.node_detectors(&node).unwrap().next().unwrap();
552 assert_eq!(
553 row.address(),
554 NodeDetectorAddress::Bundle {
555 use_index: 0,
556 row: 0
557 }
558 );
559 assert_eq!(row.measurements().count(), 2);
560 assert_eq!(
561 row.parity().terms(),
562 &[DetectorTerm::LoopState(LoopStateId(2))]
563 );
564 assert_eq!(row.coords().unwrap().collect::<Vec<_>>(), [11.0, 0.0, 3.0]);
565 }
566
567 #[test]
568 fn preflight_rejects_bad_binding_and_local_slot() {
569 let mut bloq = Bloq::new();
570 let bundle = bloq.add_detector_bundle(DetectorBundle::new(
571 vec![TemplateId(0)],
572 vec![BundleDetector {
573 parity: DetectorParity::from_measurements([BundleMeasurement {
574 owner: 1,
575 measurement: 0,
576 }]),
577 coords: None,
578 }],
579 ));
580 let mut node = QuantumNode {
581 detector_bundles: vec![DetectorBundleUse {
582 bundle,
583 instances: vec![],
584 offset: IVec2::ZERO,
585 }],
586 ..QuantumNode::default()
587 };
588 assert_eq!(
589 bloq.node_detector_count(&node),
590 Err(DetectorBundleError::BindingArity {
591 bundle,
592 expected: 1,
593 actual: 0
594 })
595 );
596 node.detector_bundles[0]
597 .instances
598 .push(TemplateInstanceId(0));
599 assert_eq!(
600 bloq.node_detector_count(&node),
601 Err(DetectorBundleError::InvalidLocalOwner {
602 owner: 1,
603 owners: 1
604 })
605 );
606 }
607
608 #[test]
609 fn binding_checker_checks_even_unused_owner_slots() {
610 let mut bloq = Bloq::new();
611 let bundle = bloq.add_detector_bundle(DetectorBundle::new(vec![TemplateId(2)], vec![]));
612 let instance = TemplateInstanceId(4);
613 let node = QuantumNode {
614 detector_bundles: vec![DetectorBundleUse {
615 bundle,
616 instances: vec![instance],
617 offset: IVec2::ZERO,
618 }],
619 ..QuantumNode::default()
620 };
621 assert_eq!(
622 bloq.check_detector_bundle_bindings(&node, |_| None),
623 Err(DetectorBundleError::UnknownBoundInstance {
624 bundle,
625 owner: 0,
626 instance,
627 })
628 );
629 assert_eq!(
630 bloq.check_detector_bundle_bindings(&node, |_| Some(TemplateId(1))),
631 Err(DetectorBundleError::OwnerTemplateMismatch {
632 bundle,
633 owner: 0,
634 instance,
635 expected: TemplateId(2),
636 actual: TemplateId(1),
637 })
638 );
639 assert_eq!(
640 bloq.check_detector_bundle_bindings(&node, |_| Some(TemplateId(2))),
641 Ok(())
642 );
643 }
644}