Skip to main content

bloq_circuit/
flow_engine.rs

1//! Flow matching across chunk boundaries.
2
3use std::collections::hash_map::Entry;
4use std::fmt;
5use std::vec::Drain;
6
7use glam::IVec2;
8use rustc_hash::FxHashMap;
9use smallvec::SmallVec;
10
11use crate::chunk::{Flow, FlowMarker};
12use crate::error::FlowError;
13use crate::pauli_map::PauliMap;
14use crate::{
15    DetectorParity, DetectorTerm, LoopCarriedDetectorState, LoopStateId,
16    checked_translate_coordinate,
17};
18
19/// Measurements carried by an open or completed flow.
20///
21/// Detector flows almost always carry one or two measurements (the current
22/// round's outcome plus the consumed previous round's), so they stay inline
23/// and flow tracking does not touch the heap per flow. The inline capacity
24/// deliberately matches [`DetectorParity`]'s term buffer so completed flows
25/// convert into parities by moving the buffer.
26pub type FlowEngineMeasurements<M> = SmallVec<[M; 2]>;
27
28/// A matched flow ready to emit as a detector.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct CompletedFlow<M> {
31    /// Every measurement fused along the chain.
32    pub measurements: FlowEngineMeasurements<M>,
33    /// XOR of the signs on every fused flow.
34    pub sign: bool,
35    /// The detector's coordinates, if the chain carries one.
36    pub center: Option<IVec2>,
37    /// Fused marker over every flow in this chain. See [`FlowMarker`]: a
38    /// `Discard` chain emits no detector; a `Restart` chain emits a restart
39    /// parity instead.
40    pub marker: FlowMarker,
41}
42
43/// A completed flow classified for detector synthesis.
44///
45/// Classification uses the fused [`FlowMarker`]. Produced by
46/// [`FlowEngine::drain_composed_chains`]; a
47/// `Discard`-marked or empty-parity chain is dropped and never appears here.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum ComposedChain<M> {
50    /// An ordinary detector at the chain's center coordinates.
51    Detector {
52        /// The detector's parity.
53        parity: DetectorParity<M>,
54        /// The detector's coordinates, if known.
55        center: Option<IVec2>,
56    },
57    /// A `RepeatUntilSuccess` restart syndrome (fused marker `Restart`); no
58    /// coordinates, as restart parities are never decoder food.
59    Restart {
60        /// The restart post-selection syndrome.
61        parity: DetectorParity<M>,
62    },
63}
64
65/// A boundary flow left open after residual composition.
66///
67/// [`FlowEngine::into_residual_flows`] returns one fused chain spliced from
68/// `start` (its origin boundary, empty for an internally-created chain) to `end`
69/// (the boundary it leaves open, empty if it closed against a boundary input),
70/// carrying every measurement composed along the way.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct ResidualFlow<M> {
73    /// The chain's origin boundary; empty for an internally-created chain.
74    pub start: PauliMap,
75    /// The boundary the chain leaves open; empty if it closed against an input.
76    pub end: PauliMap,
77    /// Every measurement composed along the chain.
78    pub measurements: FlowEngineMeasurements<M>,
79    /// XOR of the signs on every fused flow.
80    pub sign: bool,
81    /// The detector's coordinates, if any.
82    pub center: Option<IVec2>,
83    /// Carries the fused [`FlowMarker`] onto the template's `boundary_flows` so
84    /// the instance-seam pass can suppress detectors (`Discard`) or route the
85    /// cross-instance chain to a restart parity (`Restart`).
86    pub marker: FlowMarker,
87}
88
89#[derive(Clone)]
90struct OpenFlow<M> {
91    measurements: FlowEngineMeasurements<M>,
92    sign: bool,
93    center: Option<IVec2>,
94    /// Fused [`FlowMarker`] over every flow spliced into this chain so far.
95    marker: FlowMarker,
96    /// The chain's origin boundary key, threaded through fusion so the residual
97    /// can report where the chain began. Absent for an internally-created chain
98    /// (one that opened with an empty `start`); the entering key for a chain that
99    /// began at an unmatched boundary `start` under
100    /// [`UnmatchedInputMode::KeepAsResidual`]. Always absent under the `Error`
101    /// and `Skip` modes, which never admit a boundary-entering chain.
102    origin_start: Option<Box<PauliMap>>,
103}
104
105/// A chunk's flows plus their absolute XY placement offset.
106///
107/// The lower phase appends block-template flows directly with
108/// the instantiating node's offset instead of materializing translated
109/// copies per node.
110#[derive(Debug, Clone, Copy)]
111pub struct OffsetFlows<'c> {
112    /// The chunk's template-local flows.
113    pub flows: &'c [Flow],
114    /// XY offset placing the flows in absolute coordinates.
115    pub offset: IVec2,
116}
117
118impl<'c> OffsetFlows<'c> {
119    /// Pairs a flow slice with its placement offset.
120    pub fn new(flows: &'c [Flow], offset: IVec2) -> Self {
121        Self { flows, offset }
122    }
123}
124
125/// A borrowed, translated open-flow boundary key.
126///
127/// The key holds a [`PauliMap`] plus its placement offset. Hashing and equality
128/// act on the translated entries, so equal
129/// absolute boundaries match regardless of which (map, offset) pair
130/// produced them, and matching never materializes a translated map. The
131/// translated hash is computed once at construction: table growth re-hashes
132/// every stored key, so a recomputed hash would re-translate every entry on
133/// each rehash.
134#[derive(Debug, Clone, Copy)]
135pub struct FlowKey<'c> {
136    map: &'c PauliMap,
137    offset: IVec2,
138    hash: u64,
139}
140
141impl<'c> FlowKey<'c> {
142    /// Borrow a boundary at a checked placement without allocating a translated map.
143    ///
144    /// # Errors
145    ///
146    /// Returns [`crate::CoordinateOverflowError`] if a translated coordinate
147    /// exceeds the signed 32-bit lattice.
148    pub fn try_new(
149        map: &'c PauliMap,
150        offset: IVec2,
151    ) -> Result<Self, crate::CoordinateOverflowError> {
152        for (coord, _) in map {
153            checked_translate_coordinate(*coord, offset)?;
154        }
155        Ok(Self::new(map, offset))
156    }
157
158    fn new(map: &'c PauliMap, offset: IVec2) -> Self {
159        use std::hash::Hasher as _;
160        let mut hasher = rustc_hash::FxHasher::default();
161        map.hash_translated(offset, &mut hasher);
162        Self {
163            map,
164            offset,
165            hash: hasher.finish(),
166        }
167    }
168
169    fn translated(&self) -> PauliMap {
170        self.map.translated(self.offset)
171    }
172}
173
174impl std::hash::Hash for FlowKey<'_> {
175    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
176        state.write_u64(self.hash);
177    }
178}
179
180impl PartialEq for FlowKey<'_> {
181    fn eq(&self, other: &Self) -> bool {
182        if self.hash != other.hash {
183            return false;
184        }
185        if self.offset.to_array() == other.offset.to_array() {
186            return self.map == other.map;
187        }
188        self.map.len() == other.map.len()
189            && self.map.iter().zip(other.map.iter()).all(
190                |((coord, pauli), (other_coord, other_pauli))| {
191                    *coord + self.offset == *other_coord + other.offset && pauli == other_pauli
192                },
193            )
194    }
195}
196
197impl Eq for FlowKey<'_> {}
198
199impl fmt::Display for FlowKey<'_> {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        self.translated().fmt(f)
202    }
203}
204
205/// Tracks open flows and completed detectors while chunks are appended.
206///
207/// Open-flow keys borrow the boundary [`PauliMap`]s of the appended chunks
208/// (lifetime `'c`), so matching flows across chunks never clones a key. This
209/// works because every appended flow outlives the engine; the engine is a
210/// per-pass scratch structure, not a long-lived store.
211#[must_use]
212#[derive(Clone)]
213pub struct FlowEngine<'c, M> {
214    open_flows: FxHashMap<FlowKey<'c>, OpenFlow<M>>,
215    // Each group drains these payloads before publishing outputs. Keep the
216    // empty buffer for the next group instead of allocating it every round.
217    matched_inputs: Vec<Option<OpenFlow<M>>>,
218    /// Known stabilizers that commute through a temporary gauge basis but are
219    /// not themselves measured by it. They rejoin the active span when the
220    /// original basis is restored.
221    latent_flows: Vec<(PauliMap, OpenFlow<M>)>,
222    completed: Vec<CompletedFlow<M>>,
223    /// Boundary chains that closed against a boundary *input* (a flow that
224    /// entered at an unmatched `start` and later reached an empty `end`).
225    /// Populated only by [`UnmatchedInputMode::KeepAsResidual`]; empty under the
226    /// other modes, so it costs nothing when residual extraction is unused.
227    residual_completed: Vec<ResidualFlow<M>>,
228}
229
230impl<'c, M> FlowEngine<'c, M> {
231    /// Creates an empty flow-composition engine.
232    pub fn new() -> Self {
233        Self {
234            open_flows: FxHashMap::default(),
235            matched_inputs: Vec::new(),
236            latent_flows: Vec::new(),
237            completed: Vec::new(),
238            residual_completed: Vec::new(),
239        }
240    }
241
242    /// Return completed flows after requiring all active flows to terminate.
243    /// Latent stabilizers that survive the final measured-check span are output
244    /// logical correlations; observable lowering owns them, not the detector
245    /// side table.
246    ///
247    /// # Errors
248    ///
249    /// Returns [`FlowError`] if any flow remains unterminated.
250    pub fn finish(self) -> Result<Vec<CompletedFlow<M>>, FlowError> {
251        let active: Vec<String> = self.open_flows.keys().map(ToString::to_string).collect();
252        if !active.is_empty() {
253            return Err(FlowError::Composition(format!(
254                "unterminated flows: {}",
255                active.join("; ")
256            )));
257        }
258        Ok(self.completed)
259    }
260
261    /// Append one logical chunk assembled from several offset flow slices
262    /// (one per node member), while allowing previously-open boundary flows
263    /// to remain open for later chunks.
264    ///
265    /// The mapper receives the slice index alongside each measurement
266    /// reference so every member can resolve against its own template
267    /// measurement table.
268    ///
269    /// # Errors
270    ///
271    /// Returns [`FlowError`] if boundaries cannot be translated or composed.
272    pub fn append_group_allowing_dangling(
273        &mut self,
274        parts: &[OffsetFlows<'c>],
275        meas_mapper: impl FnMut(usize, &u32) -> M,
276    ) -> Result<(), FlowError> {
277        self.append_group_with_mode(parts, meas_mapper, UnmatchedInputMode::Error, None)
278    }
279
280    /// Append a boundary group while allowing its stabilizer span to differ
281    /// from the currently-open span. Exact matching remains the fast path. At
282    /// a gauge transition, products in the two spans' intersection emit
283    /// detectors; old-only generators retire and the group's ordinary creator
284    /// flows start its new-only generators.
285    ///
286    /// # Errors
287    ///
288    /// Returns [`FlowError`] if boundaries cannot be translated or composed.
289    pub fn append_group_allowing_stabilizer_transition(
290        &mut self,
291        parts: &[OffsetFlows<'c>],
292        mut meas_mapper: impl FnMut(usize, &u32) -> M,
293    ) -> Result<(), FlowError>
294    where
295        M: Copy,
296    {
297        let mut consumer_count = 0;
298        for part in parts {
299            for flow in part.flows.iter().filter(|flow| !flow.start.is_empty()) {
300                for (coord, _) in &flow.start {
301                    checked_translate_coordinate(*coord, part.offset)?;
302                }
303                consumer_count += 1;
304            }
305        }
306        if !self.latent_flows.is_empty() {
307            return self.append_stabilizer_transition(parts, &mut meas_mapper);
308        }
309
310        // Exact matching is overwhelmingly the common case. Remove each input
311        // once here and pass its payload into fusion, instead of probing it with
312        // `contains_key` and then hashing the same boundary again in
313        // `process_flow`. On a miss, restore the table before taking the
314        // stabilizer-transition path.
315        let consumer_keys = || {
316            parts.iter().flat_map(|part| {
317                part.flows
318                    .iter()
319                    .filter(|flow| !flow.start.is_empty())
320                    .map(|flow| FlowKey::new(&flow.start, part.offset))
321            })
322        };
323        let mut matched_inputs = std::mem::take(&mut self.matched_inputs);
324        matched_inputs.reserve(consumer_count);
325        for target in consumer_keys() {
326            let Some(open) = self.open_flows.remove(&target) else {
327                let duplicate_consumer = consumer_keys()
328                    .take(matched_inputs.len())
329                    .any(|seen| seen == target);
330                for (key, open) in consumer_keys().zip(matched_inputs.into_iter().flatten()) {
331                    self.open_flows.insert(key, open);
332                }
333                if !duplicate_consumer {
334                    return self.append_stabilizer_transition(parts, &mut meas_mapper);
335                }
336                // Let the ordinary path report its usual unmatched-input error.
337                return self.append_group_with_mode(
338                    parts,
339                    meas_mapper,
340                    UnmatchedInputMode::Error,
341                    None,
342                );
343            };
344            matched_inputs.push(Some(open));
345        }
346        self.append_group_with_mode(
347            parts,
348            meas_mapper,
349            UnmatchedInputMode::Error,
350            Some(matched_inputs),
351        )
352    }
353
354    /// Append chunks while ignoring flows whose start boundary is not already
355    /// open. This is useful when extracting template-local detectors: flows
356    /// that enter through a template boundary are handled later by instance
357    /// lowering, not by the reusable template side table.
358    ///
359    /// # Errors
360    ///
361    /// Returns [`FlowError`] if boundaries cannot be translated or composed.
362    pub fn append_group_skipping_unmatched_inputs(
363        &mut self,
364        parts: &[OffsetFlows<'c>],
365        meas_mapper: impl FnMut(usize, &u32) -> M,
366    ) -> Result<(), FlowError> {
367        self.append_group_with_mode(parts, meas_mapper, UnmatchedInputMode::Skip, None)
368    }
369
370    /// Append chunks for **residual extraction**: like
371    /// [`Self::append_group_skipping_unmatched_inputs`], but a flow whose `start`
372    /// is not already open is *kept* as a boundary-entering chain (recording
373    /// where it entered) rather than dropped. Combined with
374    /// [`Self::into_residual_flows`], this composes a template's open boundary
375    /// flows into fused chains in one pass: internal detectors fall out via
376    /// [`Self::drain_completed`] as usual, while every flow touching the boundary
377    /// survives in the residual.
378    ///
379    /// # Errors
380    ///
381    /// Returns [`FlowError`] if boundaries cannot be translated or composed.
382    pub fn append_residual_group(
383        &mut self,
384        parts: &[OffsetFlows<'c>],
385        meas_mapper: impl FnMut(usize, &u32) -> M,
386    ) -> Result<(), FlowError> {
387        self.append_group_with_mode(parts, meas_mapper, UnmatchedInputMode::KeepAsResidual, None)
388    }
389
390    fn append_group_with_mode(
391        &mut self,
392        parts: &[OffsetFlows<'c>],
393        mut meas_mapper: impl FnMut(usize, &u32) -> M,
394        unmatched_input_mode: UnmatchedInputMode,
395        matched_inputs: Option<Vec<Option<OpenFlow<M>>>>,
396    ) -> Result<(), FlowError> {
397        let mut inputs = match matched_inputs {
398            Some(inputs) => inputs,
399            None => {
400                let mut inputs = std::mem::take(&mut self.matched_inputs);
401                for part in parts {
402                    for flow in part.flows.iter().filter(|flow| !flow.start.is_empty()) {
403                        for (coord, _) in &flow.start {
404                            checked_translate_coordinate(*coord, part.offset)?;
405                        }
406                        inputs.push(
407                            self.open_flows
408                                .remove(&FlowKey::new(&flow.start, part.offset)),
409                        );
410                    }
411                }
412                inputs
413            }
414        };
415        let mut matched_inputs = inputs.drain(..);
416        // Only non-empty outputs occupy the table. Consumer-only detector
417        // flows can outnumber creators, and need no hash-table capacity.
418        self.open_flows.reserve(
419            parts
420                .iter()
421                .flat_map(|part| part.flows)
422                .filter(|flow| !flow.end.is_empty())
423                .count(),
424        );
425
426        // Consumers must see previous open flows before creators can replace
427        // keys, across all of the chunk's member slices.
428        for (part_index, part) in parts.iter().enumerate() {
429            for flow in part.flows {
430                if !flow.start.is_empty() {
431                    let previous = matched_inputs
432                        .next()
433                        .expect("pre-matched inputs cover every consumer");
434                    self.process_flow(
435                        flow,
436                        part.offset,
437                        part_index,
438                        &mut meas_mapper,
439                        unmatched_input_mode,
440                        previous,
441                    )?;
442                }
443            }
444        }
445        debug_assert!(
446            matched_inputs.as_slice().is_empty(),
447            "every pre-matched input is consumed"
448        );
449        drop(matched_inputs);
450        self.matched_inputs = inputs;
451        for (part_index, part) in parts.iter().enumerate() {
452            for flow in part.flows {
453                if flow.start.is_empty() {
454                    self.process_flow(
455                        flow,
456                        part.offset,
457                        part_index,
458                        &mut meas_mapper,
459                        unmatched_input_mode,
460                        None,
461                    )?;
462                }
463            }
464        }
465
466        Ok(())
467    }
468
469    /// Drains the completed (internal, boundary-detached) detector chains,
470    /// leaving open flows untouched for later chunks.
471    pub fn drain_completed(&mut self) -> Drain<'_, CompletedFlow<M>> {
472        self.completed.drain(..)
473    }
474
475    /// Consume the engine after a residual composition (chunks appended with
476    /// [`Self::append_residual_group`]) and return every boundary flow as a
477    /// fused chain: the chains still open (`origin_start → open key`) plus those
478    /// that closed against a boundary input (`origin_start → ∅`). Chains that
479    /// closed with an empty origin are internal detectors; they collect in
480    /// `completed` (drainable via [`Self::drain_completed`]) and are excluded here.
481    ///
482    /// The caller owns ordering: `open_flows` iterates in hash order, so a caller
483    /// that needs determinism (e.g. reproducible detector emission) must sort the
484    /// result by a canonical key.
485    pub fn into_residual_flows(self) -> Vec<ResidualFlow<M>> {
486        let mut flows = self.residual_completed;
487        flows.extend(
488            self.open_flows
489                .into_iter()
490                .map(|(key, open)| (key.translated(), open))
491                .chain(self.latent_flows)
492                .map(|(end, open)| ResidualFlow {
493                    start: open
494                        .origin_start
495                        .map_or_else(PauliMap::empty, |start| *start),
496                    end,
497                    measurements: open.measurements,
498                    sign: open.sign,
499                    center: open.center,
500                    marker: open.marker,
501                }),
502        );
503        flows
504    }
505
506    fn process_flow(
507        &mut self,
508        flow: &'c Flow,
509        offset: IVec2,
510        part_index: usize,
511        meas_mapper: &mut impl FnMut(usize, &u32) -> M,
512        unmatched_input_mode: UnmatchedInputMode,
513        previous: Option<OpenFlow<M>>,
514    ) -> Result<(), FlowError> {
515        // Flow keys translate coordinates inside Hash/Eq. Check before a key
516        // reaches either trait so malformed API/binary input returns an error
517        // instead of debug-panicking or release-wrapping.
518        if offset != IVec2::ZERO {
519            for (coord, _) in flow.start.iter().chain(flow.end.iter()) {
520                checked_translate_coordinate(*coord, offset)?;
521            }
522            if let Some(center) = flow.center {
523                checked_translate_coordinate(center, offset)?;
524            }
525        }
526
527        let mut measurements: FlowEngineMeasurements<M> =
528            SmallVec::with_capacity(flow.measurements.len());
529        for measurement in &flow.measurements {
530            measurements.push(meas_mapper(part_index, measurement));
531        }
532
533        let mut center = flow.center.map(|center| center + offset);
534        let mut sign = flow.sign;
535        let mut marker = flow.marker;
536        // The chain's origin boundary, carried so a residual composition can
537        // report where each open chain began. Empty unless this flow enters at
538        // an unmatched boundary `start` (see `KeepAsResidual` below) or inherits
539        // a non-empty origin from the chain it consumes.
540        let mut origin_start = None;
541
542        if !flow.start.is_empty() {
543            // Inputs were removed before any output of this group was published.
544            match previous {
545                Some(prev) => {
546                    // The chunk's own measurements come first, then the
547                    // carried history.
548                    measurements.extend(prev.measurements);
549                    sign ^= prev.sign;
550                    center = center.or(prev.center);
551                    origin_start = prev.origin_start;
552                    // Two flows fusing into one chain can mix discard and
553                    // restart; downstream routing checks `Discard` first, so a
554                    // fall-through would silently drop the restart parity. This
555                    // is a topology-dependent conflict, so reject rather than panic.
556                    marker = marker.fuse(prev.marker).ok_or_else(|| {
557                        FlowError::Composition(
558                            "fused chain mixes discard and restart components".into(),
559                        )
560                    })?;
561                }
562                None => match unmatched_input_mode {
563                    UnmatchedInputMode::Skip => return Ok(()),
564                    // A boundary input: keep the chain open, recording where it
565                    // entered so the residual can close it against a neighbor.
566                    UnmatchedInputMode::KeepAsResidual => {
567                        origin_start = Some(Box::new(flow.start.translated(offset)));
568                    }
569                    UnmatchedInputMode::Error => {
570                        return Err(FlowError::Composition(format!(
571                            "unmatched flow input {}",
572                            flow.start.translated(offset)
573                        )));
574                    }
575                },
576            }
577        }
578
579        if !flow.end.is_empty() {
580            // Flow continues: store for future matching
581            let end_key = FlowKey::new(&flow.end, offset);
582            let Entry::Vacant(entry) = self.open_flows.entry(end_key) else {
583                return Err(FlowError::Composition(format!(
584                    "multiple flows create boundary {end_key}"
585                )));
586            };
587            entry.insert(OpenFlow {
588                measurements,
589                sign,
590                center,
591                origin_start,
592                marker,
593            });
594        } else if !measurements.is_empty() || sign || marker == FlowMarker::Discard {
595            // A chain reached an empty end. If it began at a boundary input
596            // (non-empty origin) it is a residual flow that closes against a
597            // neighbor; otherwise it closed entirely within this composition (an
598            // internal detector). Non-residual modes never set a non-empty
599            // origin, so they always take the `completed` branch.
600            //
601            // A measurement-free chain is normally dropped (it can never carry a
602            // detector parity). A *discarding* one is kept: it exists precisely to
603            // consume an incoming seam stabilizer with no reconstruction of its
604            // own (a selective block's opposite-basis stabilizer), so it must
605            // survive onto the residual to match that seam and suppress its
606            // detector. `discard` is always false in the non-selective paths.
607            if let Some(origin_start) = origin_start {
608                self.residual_completed.push(ResidualFlow {
609                    start: *origin_start,
610                    end: PauliMap::empty(),
611                    measurements,
612                    sign,
613                    center,
614                    marker,
615                });
616            } else {
617                self.completed.push(CompletedFlow {
618                    measurements,
619                    sign,
620                    center,
621                    marker,
622                });
623            }
624        }
625
626        Ok(())
627    }
628
629    fn append_stabilizer_transition(
630        &mut self,
631        parts: &[OffsetFlows<'c>],
632        meas_mapper: &mut impl FnMut(usize, &u32) -> M,
633    ) -> Result<(), FlowError>
634    where
635        M: Copy,
636    {
637        let mut incoming = Vec::new();
638        for (part_index, part) in parts.iter().enumerate() {
639            for flow in part.flows.iter().filter(|flow| !flow.start.is_empty()) {
640                if !flow.end.is_empty() {
641                    return Err(FlowError::Composition(
642                        "stabilizer-span transitions require boundary consumer flows".into(),
643                    ));
644                }
645                let mut payload = empty_open_flow();
646                payload.measurements.extend(
647                    flow.measurements
648                        .iter()
649                        .map(|measurement| meas_mapper(part_index, measurement)),
650                );
651                payload.sign = flow.sign;
652                payload.center = flow
653                    .center
654                    .map(|center| checked_translate_coordinate(center, part.offset))
655                    .transpose()?;
656                payload.marker = flow.marker;
657                incoming.push((flow.start.translated(part.offset), payload));
658            }
659        }
660
661        let mut old: Vec<_> = std::mem::take(&mut self.open_flows)
662            .into_iter()
663            .map(|(key, flow)| (key.translated(), flow))
664            .chain(std::mem::take(&mut self.latent_flows))
665            .collect();
666        old.sort_by(|(a, _), (b, _)| pauli_map_cmp(a, b));
667        let old_len = old.len();
668        // Borrow the boundaries: reduction clones each one as it consumes it.
669        let generators: Vec<&PauliMap> = old
670            .iter()
671            .map(|(map, _)| map)
672            .chain(incoming.iter().map(|(start, _)| start))
673            .collect();
674        let dependencies = pauli_dependencies(&generators);
675        for (index, mask) in dependencies {
676            if index < old_len {
677                return Err(FlowError::Composition(
678                    "open flow boundaries are not independent generators".into(),
679                ));
680            }
681            if first_set_bit(&mask).is_none_or(|first| first >= old_len) {
682                return Err(FlowError::Composition(
683                    "incoming flow boundaries are not independent generators".into(),
684                ));
685            }
686            let mut completed = empty_open_flow();
687            let mut product = PauliMap::empty();
688            for (index, (map, flow)) in incoming.iter().enumerate() {
689                if mask[(index + old_len) / 64] & (1 << ((index + old_len) % 64)) != 0 {
690                    multiply_pauli_flow(&mut product, &mut completed, map, flow)?;
691                }
692            }
693            for (index, (map, flow)) in old.iter().enumerate() {
694                if mask[index / 64] & (1 << (index % 64)) != 0 {
695                    multiply_pauli_flow(&mut product, &mut completed, map, flow)?;
696                }
697            }
698            if !completed.measurements.is_empty()
699                || completed.sign
700                || completed.marker == FlowMarker::Discard
701            {
702                self.completed.push(CompletedFlow {
703                    measurements: completed.measurements,
704                    sign: completed.sign,
705                    center: completed.center,
706                    marker: completed.marker,
707                });
708            }
709        }
710
711        // Measuring a new stabilizer destroys one anticommuting old generator;
712        // products of the remaining anticommuting generators with that pivot
713        // survive. This is the usual stabilizer-tableau measurement update.
714        let mut commutations = pauli_commutation_columns(
715            old.iter().map(|(map, _)| map),
716            incoming.iter().map(|(map, _)| map),
717        );
718        for measured in 0..incoming.len() {
719            let mut targets = std::mem::take(&mut commutations[measured]);
720            let Some(pivot) = first_set_bit(&targets) else {
721                continue;
722            };
723            let last = old.len() - 1;
724            let (pivot_map, pivot_flow) = old.swap_remove(pivot);
725            swap_remove_bit(&mut targets, pivot, last);
726            for (word, &bits) in targets.iter().enumerate() {
727                let mut bits = bits;
728                while bits != 0 {
729                    let target = word * 64 + bits.trailing_zeros() as usize;
730                    let (known, flow) = &mut old[target];
731                    multiply_pauli_flow(known, flow, &pivot_map, &pivot_flow)?;
732                    bits &= bits - 1;
733                }
734            }
735            // Commutation is bilinear: multiplying by the pivot toggles exactly
736            // its anticommuting columns. Keep the original swap-remove order.
737            for column in &mut commutations[measured + 1..] {
738                let anticommutes = column[pivot / 64] & (1 << (pivot % 64)) != 0;
739                swap_remove_bit(column, pivot, last);
740                if anticommutes {
741                    xor_words(column, &targets);
742                }
743            }
744        }
745
746        // Keep only surviving old stabilizers outside the newly measured span.
747        // Exact active checks are represented by the creator flows below.
748        let mut basis = FxHashMap::default();
749        for (measured, flow) in &incoming {
750            let mut measured = measured.clone();
751            let mut flow = flow.clone();
752            reduce_pauli_flow(&mut measured, &mut flow, &basis)?;
753            let pivot = pauli_pivot(&measured)
754                .expect("incoming flow boundaries were checked as independent");
755            basis.insert(pivot, (measured, flow));
756        }
757        for (mut known, mut flow) in old {
758            reduce_pauli_flow(&mut known, &mut flow, &basis)?;
759            let Some(pivot) = pauli_pivot(&known) else {
760                continue;
761            };
762            basis.insert(pivot, (known.clone(), flow.clone()));
763            self.latent_flows.push((known, flow));
764        }
765
766        // The incoming consumers have now either contributed to an intersection
767        // detector or initialized a new-only gauge check. In both cases they do
768        // not remain open. The group's creator flows establish the new boundary
769        // basis for the next node.
770        for (part_index, part) in parts.iter().enumerate() {
771            for flow in part.flows.iter().filter(|flow| flow.start.is_empty()) {
772                self.process_flow(
773                    flow,
774                    part.offset,
775                    part_index,
776                    meas_mapper,
777                    UnmatchedInputMode::Error,
778                    None,
779                )?;
780            }
781        }
782        Ok(())
783    }
784}
785
786type PauliPivot = (i32, i32, u8);
787
788fn pauli_pivot(map: &PauliMap) -> Option<PauliPivot> {
789    map.iter().next_back().map(|(coord, pauli)| {
790        let component = if *pauli & crate::Pauli::Z { 2 } else { 1 };
791        (coord.x, coord.y, component)
792    })
793}
794
795// Row XOR can only introduce bits below its pivot. Retained higher bits
796// never need revisiting, so walk present bits instead of scanning every basis row.
797fn pauli_pivot_before(map: &PauliMap, before: PauliPivot) -> Option<PauliPivot> {
798    let maximum = pauli_pivot(map)?;
799    if maximum < before {
800        return Some(maximum);
801    }
802    let (x, y, component) = before;
803    let coord = IVec2::new(x, y);
804    if component == 2
805        && map
806            .get(&coord)
807            .is_some_and(|pauli| *pauli & crate::Pauli::X)
808    {
809        return Some((x, y, 1));
810    }
811    map.entry_before(coord).map(|(coord, pauli)| {
812        (
813            coord.x,
814            coord.y,
815            if pauli & crate::Pauli::Z { 2 } else { 1 },
816        )
817    })
818}
819
820#[cfg(test)]
821fn has_pauli_pivot(map: &PauliMap, pivot: PauliPivot) -> bool {
822    let (x, y, component) = pivot;
823    map.get(&IVec2::new(x, y)).is_some_and(|pauli| {
824        *pauli
825            & if component == 2 {
826                crate::Pauli::Z
827            } else {
828                crate::Pauli::X
829            }
830    })
831}
832
833/// Columns name incoming measurements; each bit names an old generator.
834/// Construct only shared-coordinate pairs instead of testing every row pair.
835fn pauli_commutation_columns<'a>(
836    known: impl ExactSizeIterator<Item = &'a PauliMap>,
837    measured: impl Iterator<Item = &'a PauliMap>,
838) -> Vec<Vec<u64>> {
839    let words = known.len().div_ceil(64);
840    let mut sites: FxHashMap<IVec2, SmallVec<[(usize, crate::Pauli); 4]>> = FxHashMap::default();
841    for (index, map) in known.enumerate() {
842        for (&coord, &pauli) in map {
843            sites.entry(coord).or_default().push((index, pauli));
844        }
845    }
846    measured
847        .map(|map| {
848            let mut column = vec![0; words];
849            for (coord, &pauli) in map {
850                for &(index, other) in sites.get(coord).into_iter().flatten() {
851                    if pauli.anticommutes(other) {
852                        column[index / 64] ^= 1 << (index % 64);
853                    }
854                }
855            }
856            column
857        })
858        .collect()
859}
860
861fn swap_remove_bit(words: &mut [u64], index: usize, last: usize) {
862    let value = (words[last / 64] >> (last % 64)) & 1;
863    words[index / 64] = (words[index / 64] & !(1 << (index % 64))) | (value << (index % 64));
864    words[last / 64] &= !(1 << (last % 64));
865}
866
867fn reduce_pauli_flow<M: Copy>(
868    value: &mut PauliMap,
869    flow: &mut OpenFlow<M>,
870    basis: &FxHashMap<PauliPivot, (PauliMap, OpenFlow<M>)>,
871) -> Result<(), FlowError> {
872    let mut pivot = pauli_pivot(value);
873    while let Some(current) = pivot {
874        if let Some((row, row_flow)) = basis.get(&current) {
875            multiply_pauli_flow(value, flow, row, row_flow)?;
876        }
877        pivot = pauli_pivot_before(value, current);
878    }
879    Ok(())
880}
881
882fn multiply_pauli_flow<M: Copy>(
883    target: &mut PauliMap,
884    flow: &mut OpenFlow<M>,
885    source: &PauliMap,
886    source_flow: &OpenFlow<M>,
887) -> Result<(), FlowError> {
888    let phase = target.product_phase(source);
889    if !phase.is_multiple_of(2) {
890        return Err(FlowError::Composition(
891            "stabilizer-span products require commuting boundaries".into(),
892        ));
893    }
894    flow.sign ^= phase == 2;
895    *target = &*target ^ source;
896    xor_open_flow(flow, source_flow)
897}
898
899fn first_set_bit(words: &[u64]) -> Option<usize> {
900    words
901        .iter()
902        .enumerate()
903        .find_map(|(word, &bits)| (bits != 0).then(|| word * 64 + bits.trailing_zeros() as usize))
904}
905
906fn xor_words(target: &mut [u64], source: &[u64]) {
907    for (target, source) in target.iter_mut().zip(source) {
908        *target ^= source;
909    }
910}
911
912#[cfg(test)]
913fn xor_bits(target: &mut [bool], source: &[bool]) {
914    for (target, source) in target.iter_mut().zip(source) {
915        *target ^= source;
916    }
917}
918
919fn pauli_dependencies(generators: &[&PauliMap]) -> Vec<(usize, Vec<u64>)> {
920    let mut basis: FxHashMap<PauliPivot, (PauliMap, Vec<u64>)> = FxHashMap::default();
921    let mut dependencies = Vec::new();
922    for (index, generator) in generators.iter().enumerate() {
923        let mut value = (*generator).clone();
924        let mut mask = vec![0; generators.len().div_ceil(64)];
925        mask[index / 64] = 1 << (index % 64);
926        let mut pivot = pauli_pivot(&value);
927        while let Some(current) = pivot {
928            if let Some((row, row_mask)) = basis.get(&current) {
929                value = &value ^ row;
930                xor_words(&mut mask, row_mask);
931            }
932            pivot = pauli_pivot_before(&value, current);
933        }
934        if let Some(pivot) = pauli_pivot(&value) {
935            basis.insert(pivot, (value, mask));
936        } else {
937            dependencies.push((index, mask));
938        }
939    }
940    dependencies
941}
942
943#[cfg(test)]
944fn eager_pauli_dependencies(generators: &[&PauliMap]) -> Vec<(usize, Vec<bool>)> {
945    let mut basis: Vec<(PauliPivot, PauliMap, Vec<bool>)> = Vec::new();
946    let mut dependencies = Vec::new();
947    for (index, generator) in generators.iter().enumerate() {
948        let mut value = (*generator).clone();
949        let mut mask = vec![false; generators.len()];
950        mask[index] = true;
951        let start = pauli_pivot(&value).map_or(basis.len(), |maximum| {
952            basis.partition_point(|(pivot, _, _)| *pivot > maximum)
953        });
954        for (pivot, row, row_mask) in &basis[start..] {
955            if has_pauli_pivot(&value, *pivot) {
956                value = &value ^ row;
957                xor_bits(&mut mask, row_mask);
958                if value.is_empty() {
959                    break;
960                }
961            }
962        }
963        if let Some(pivot) = pauli_pivot(&value) {
964            let index = basis.partition_point(|(existing, _, _)| *existing > pivot);
965            basis.insert(index, (pivot, value, mask));
966        } else {
967            dependencies.push((index, mask));
968        }
969    }
970    dependencies
971}
972
973fn empty_open_flow<M>() -> OpenFlow<M> {
974    OpenFlow {
975        measurements: SmallVec::new(),
976        sign: false,
977        center: None,
978        marker: FlowMarker::Detector,
979        origin_start: None,
980    }
981}
982
983fn xor_open_flow<M: Copy>(target: &mut OpenFlow<M>, source: &OpenFlow<M>) -> Result<(), FlowError> {
984    target.measurements.extend_from_slice(&source.measurements);
985    target.sign ^= source.sign;
986    target.center = target.center.or(source.center);
987    target.marker = target.marker.fuse(source.marker).ok_or_else(|| {
988        FlowError::Composition(
989            "stabilizer-span transition mixes discard and restart components".into(),
990        )
991    })?;
992    if let Some(source_origin) = source.origin_start.as_deref() {
993        target.origin_start = match target.origin_start.take() {
994            Some(mut target_origin) => {
995                let phase = target_origin.product_phase(source_origin);
996                if !phase.is_multiple_of(2) {
997                    return Err(FlowError::Composition(
998                        "stabilizer-span products require commuting origin boundaries".into(),
999                    ));
1000                }
1001                target.sign ^= phase == 2;
1002                *target_origin = &*target_origin ^ source_origin;
1003                (!target_origin.is_empty()).then_some(target_origin)
1004            }
1005            None => Some(Box::new(source_origin.clone())),
1006        };
1007    }
1008    Ok(())
1009}
1010
1011fn pauli_map_cmp(a: &PauliMap, b: &PauliMap) -> std::cmp::Ordering {
1012    a.iter()
1013        .map(|(coord, pauli)| (coord.x, coord.y, *pauli as u8))
1014        .cmp(
1015            b.iter()
1016                .map(|(coord, pauli)| (coord.x, coord.y, *pauli as u8)),
1017        )
1018}
1019
1020#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1021enum UnmatchedInputMode {
1022    Error,
1023    Skip,
1024    /// Keep an unmatched-`start` flow as a boundary-entering residual chain
1025    /// (used by [`FlowEngine::append_residual_group`]).
1026    KeepAsResidual,
1027}
1028
1029impl<M> Default for FlowEngine<'_, M> {
1030    fn default() -> Self {
1031        Self::new()
1032    }
1033}
1034
1035impl<M: fmt::Debug> fmt::Debug for FlowEngine<'_, M> {
1036    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1037        f.debug_struct("FlowEngine")
1038            .field("open_flows", &self.open_flows.len())
1039            .field("latent_flows", &self.latent_flows.len())
1040            .field("completed", &self.completed.len())
1041            .finish()
1042    }
1043}
1044
1045/// Loop-carried detector states opened for a loop body, awaiting their `next`
1046/// parity once the body's closing flows are known.
1047///
1048/// Produced by [`FlowEngine::start_loop_detector_states`] and consumed by
1049/// [`FlowEngine::finish_loop_detector_states`].
1050#[derive(Debug, Clone)]
1051pub struct PendingLoopDetectorStates<'c, M = u32> {
1052    loop_states: Vec<(FlowKey<'c>, LoopCarriedDetectorState<M>)>,
1053}
1054
1055impl<'c, M: Copy + Ord> FlowEngine<'c, DetectorTerm<M>> {
1056    /// Assign a loop-carried detector state to every open flow the loop body
1057    /// is about to consume: the open measurements become the state's initial
1058    /// parity and are replaced by a single loop-state term.
1059    ///
1060    /// Open flows are matched against the first body chunk's start keys
1061    /// directly; a valid body never consumes the same key twice (the append
1062    /// would fail), so each open flow gains at most one state.
1063    ///
1064    /// # Errors
1065    ///
1066    /// Returns [`FlowError`] if coordinates or the loop-state id space overflow.
1067    ///
1068    /// # Panics
1069    ///
1070    /// Panics if the preflighted loop-state range is internally inconsistent.
1071    pub fn start_loop_detector_states(
1072        &mut self,
1073        body_first_chunk: &[OffsetFlows<'c>],
1074        next_loop_state: &mut u32,
1075    ) -> Result<PendingLoopDetectorStates<'c, M>, FlowError> {
1076        for part in body_first_chunk
1077            .iter()
1078            .filter(|part| part.offset != IVec2::ZERO)
1079        {
1080            for flow in part.flows.iter().filter(|flow| !flow.start.is_empty()) {
1081                for (coordinate, _) in &flow.start {
1082                    checked_translate_coordinate(*coordinate, part.offset)?;
1083                }
1084            }
1085        }
1086        let possible_states =
1087            loop_state_upper_bound(body_first_chunk.iter().map(|part| part.flows.len()));
1088        if possible_states == usize::MAX || possible_states > (u32::MAX - *next_loop_state) as usize
1089        {
1090            self.check_loop_state_capacity(body_first_chunk, *next_loop_state)?;
1091        }
1092
1093        let mut loop_states = Vec::with_capacity(self.open_flows.len().min(possible_states));
1094        for part in body_first_chunk {
1095            for flow in part.flows {
1096                if flow.start.is_empty() {
1097                    continue;
1098                }
1099                let key = FlowKey::new(&flow.start, part.offset);
1100                let Some(open) = self.open_flows.get_mut(&key) else {
1101                    continue;
1102                };
1103                let state = LoopStateId(*next_loop_state);
1104                *next_loop_state = next_loop_state
1105                    .checked_add(1)
1106                    .expect("loop state range was preflighted");
1107                let initial = DetectorParity::from_term_buf(std::mem::replace(
1108                    &mut open.measurements,
1109                    smallvec::smallvec![DetectorTerm::LoopState(state)],
1110                ))
1111                .with_sign(std::mem::take(&mut open.sign));
1112                loop_states.push((
1113                    key,
1114                    LoopCarriedDetectorState {
1115                        state,
1116                        initial,
1117                        next: DetectorParity::default(),
1118                    },
1119                ));
1120            }
1121        }
1122        Ok(PendingLoopDetectorStates { loop_states })
1123    }
1124
1125    #[cold]
1126    fn check_loop_state_capacity(
1127        &self,
1128        body_first_chunk: &[OffsetFlows<'c>],
1129        next_loop_state: u32,
1130    ) -> Result<(), FlowError> {
1131        body_first_chunk
1132            .iter()
1133            .flat_map(|part| {
1134                part.flows.iter().filter(|flow| {
1135                    !flow.start.is_empty()
1136                        && self
1137                            .open_flows
1138                            .contains_key(&FlowKey::new(&flow.start, part.offset))
1139                })
1140            })
1141            .try_fold(next_loop_state, |next, _| next.checked_add(1))
1142            .ok_or_else(|| {
1143                FlowError::Composition("loop detector state id space exhausted".into())
1144            })?;
1145        Ok(())
1146    }
1147
1148    /// Drain completed chains, classifying each by its fused [`FlowMarker`] into
1149    /// a [`ComposedChain`]: a `Discard`-marked or empty-parity chain produces
1150    /// nothing, a `Restart` chain becomes a restart syndrome, everything else a
1151    /// detector at the chain's center. This is the shared marker discipline for
1152    /// detector synthesis — the compile-time boundary pass
1153    /// (`bloq_compile::lower::detector`) and the seam recompose
1154    /// (`bloq_ir::edit`) both drain through here so the two cannot drift.
1155    pub fn drain_composed_chains(&mut self) -> impl Iterator<Item = ComposedChain<M>> + '_ {
1156        self.completed.drain(..).filter_map(|completed| {
1157            if completed.marker == FlowMarker::Discard {
1158                return None;
1159            }
1160            let parity =
1161                DetectorParity::from_term_buf(completed.measurements).with_sign(completed.sign);
1162            if parity.is_empty() {
1163                return None;
1164            }
1165            Some(if completed.marker == FlowMarker::Restart {
1166                ComposedChain::Restart { parity }
1167            } else {
1168                ComposedChain::Detector {
1169                    parity,
1170                    center: completed.center,
1171                }
1172            })
1173        })
1174    }
1175
1176    /// Completes the pending loop states: each state's `next` parity is read
1177    /// from the flow still open at its key after the body was appended.
1178    pub fn finish_loop_detector_states(
1179        &self,
1180        pending: PendingLoopDetectorStates<'c, M>,
1181    ) -> Vec<LoopCarriedDetectorState<M>> {
1182        pending
1183            .loop_states
1184            .into_iter()
1185            .map(|(key, mut detector_state)| {
1186                if let Some(open) = self.open_flows.get(&key) {
1187                    detector_state.next =
1188                        DetectorParity::from_terms(open.measurements.iter().copied())
1189                            .with_sign(open.sign);
1190                }
1191                detector_state
1192            })
1193            .collect()
1194    }
1195}
1196
1197fn loop_state_upper_bound(lengths: impl IntoIterator<Item = usize>) -> usize {
1198    lengths.into_iter().fold(0, usize::saturating_add)
1199}
1200
1201#[cfg(test)]
1202mod tests {
1203    use super::*;
1204    use crate::Pauli;
1205
1206    #[test]
1207    fn repeated_flow_slices_cannot_wrap_the_loop_state_bound() {
1208        assert_eq!(
1209            loop_state_upper_bound([usize::MAX / 2, usize::MAX / 2, 2]),
1210            usize::MAX,
1211        );
1212    }
1213    fn pauli_map(entries: impl IntoIterator<Item = (IVec2, Pauli)>) -> PauliMap {
1214        entries.into_iter().collect()
1215    }
1216
1217    fn x(coord: IVec2) -> PauliMap {
1218        pauli_map([(coord, Pauli::X)])
1219    }
1220
1221    fn z(coord: IVec2) -> PauliMap {
1222        pauli_map([(coord, Pauli::Z)])
1223    }
1224
1225    fn flow(start: PauliMap, end: PauliMap, measurement: Option<u32>) -> Flow {
1226        Flow::new(start, end).with_measurements(measurement)
1227    }
1228
1229    fn append_flows<'c>(
1230        engine: &mut FlowEngine<'c, u32>,
1231        flows: &'c [Flow],
1232        mut mapper: impl FnMut(&u32) -> u32,
1233    ) -> Result<(), FlowError> {
1234        engine.append_group_allowing_dangling(&[OffsetFlows::new(flows, IVec2::ZERO)], |_, m| {
1235            mapper(m)
1236        })
1237    }
1238
1239    fn measurement_to_sequential_mapper(offset: &mut u32) -> impl FnMut(&u32) -> u32 + '_ {
1240        move |_reference| {
1241            let id = *offset;
1242            *offset += 1;
1243            id
1244        }
1245    }
1246
1247    #[test]
1248    fn offset_flows_match_equal_absolute_boundaries() {
1249        let mut engine = FlowEngine::<u32>::new();
1250        let mut offset = 0u32;
1251
1252        // Creator placed at (2, 0): its local (0, 0) boundary sits at the
1253        // absolute coordinate the consumer (placed at the origin) names
1254        // explicitly.
1255        let creator = [Flow::new(PauliMap::empty(), x(IVec2::new(0, 0)))
1256            .with_measurements([0])
1257            .with_center(IVec2::new(1, 1))];
1258        let consumer = [flow(x(IVec2::new(2, 0)), PauliMap::empty(), Some(0))];
1259
1260        let mut mapper = measurement_to_sequential_mapper(&mut offset);
1261        engine
1262            .append_group_allowing_dangling(
1263                &[OffsetFlows::new(&creator, IVec2::new(2, 0))],
1264                |_, m| mapper(m),
1265            )
1266            .expect("creator");
1267        engine
1268            .append_group_allowing_dangling(&[OffsetFlows::new(&consumer, IVec2::ZERO)], |_, m| {
1269                mapper(m)
1270            })
1271            .expect("consumer matches translated boundary");
1272
1273        let completed = engine.finish().expect("clean finish");
1274        assert_eq!(completed.len(), 1);
1275        assert_eq!(completed[0].measurements.as_slice(), [1, 0]);
1276        assert_eq!(completed[0].center, Some(IVec2::new(3, 1)));
1277    }
1278
1279    #[test]
1280    fn offset_flow_coordinate_overflow_is_typed() {
1281        let coordinate = IVec2::new(i32::MAX, 0);
1282        let offset = IVec2::new(1, 0);
1283        let flows = [Flow::new(PauliMap::empty(), x(coordinate))];
1284        let error = FlowEngine::<u32>::new()
1285            .append_group_allowing_dangling(&[OffsetFlows::new(&flows, offset)], |_, &m| m)
1286            .unwrap_err();
1287
1288        assert_eq!(
1289            error,
1290            FlowError::CoordinateOverflow(crate::CoordinateOverflowError { coordinate, offset })
1291        );
1292    }
1293
1294    #[test]
1295    fn stabilizer_transition_center_overflow_is_typed() {
1296        let coordinate = IVec2::new(i32::MAX, 0);
1297        let offset = IVec2::X;
1298        let incoming = [flow(z(IVec2::ZERO), PauliMap::empty(), Some(1)).with_center(coordinate)];
1299        let mut engine = FlowEngine::<u32>::new();
1300
1301        let error = engine
1302            .append_group_allowing_stabilizer_transition(
1303                &[OffsetFlows::new(&incoming, offset)],
1304                |_, &measurement| measurement,
1305            )
1306            .unwrap_err();
1307
1308        assert_eq!(
1309            error,
1310            FlowError::CoordinateOverflow(crate::CoordinateOverflowError { coordinate, offset })
1311        );
1312    }
1313
1314    #[test]
1315    fn zero_offset_accepts_coordinate_extrema() {
1316        let boundary = x(IVec2::new(i32::MAX, i32::MIN));
1317        let flows = [Flow::new(PauliMap::empty(), boundary)];
1318
1319        FlowEngine::<u32>::new()
1320            .append_group_allowing_dangling(
1321                &[OffsetFlows::new(&flows, IVec2::ZERO)],
1322                |_, &measurement| measurement,
1323            )
1324            .expect("zero offset cannot overflow a coordinate");
1325    }
1326
1327    #[test]
1328    fn completes_and_fuses_flows() {
1329        let mut engine = FlowEngine::<u32>::new();
1330        let mut offset = 0u32;
1331        let boundary = x(IVec2::new(0, 0));
1332        let chunk1 = [Flow::new(PauliMap::empty(), boundary.clone())
1333            .with_measurements([0])
1334            .with_sign(true)
1335            .with_center(IVec2::new(1, 1))];
1336        let chunk2 = [Flow::new(boundary, PauliMap::empty()).with_measurements([0])];
1337
1338        append_flows(
1339            &mut engine,
1340            &chunk1,
1341            measurement_to_sequential_mapper(&mut offset),
1342        )
1343        .expect("chunk1");
1344        append_flows(
1345            &mut engine,
1346            &chunk2,
1347            measurement_to_sequential_mapper(&mut offset),
1348        )
1349        .expect("chunk2");
1350
1351        let completed = engine.finish().expect("clean finish");
1352        assert_eq!(completed.len(), 1);
1353        assert_eq!(completed[0].measurements.as_slice(), [1, 0]);
1354        assert!(completed[0].sign);
1355        assert_eq!(completed[0].center, Some(IVec2::new(1, 1)));
1356    }
1357
1358    #[test]
1359    fn boundary_composition_accepts_an_equivalent_generator_basis() {
1360        let a = x(IVec2::new(0, 0));
1361        let b = z(IVec2::new(1, 0));
1362        let ab = &a ^ &b;
1363        let creators = [
1364            flow(PauliMap::empty(), a.clone(), Some(0)),
1365            flow(PauliMap::empty(), b, Some(1)),
1366        ];
1367        let consumers = [
1368            flow(ab, PauliMap::empty(), Some(2)),
1369            flow(a, PauliMap::empty(), Some(3)),
1370        ];
1371        let mut engine = FlowEngine::<u32>::new();
1372        engine
1373            .append_group_allowing_dangling(
1374                &[OffsetFlows::new(&creators, IVec2::ZERO)],
1375                |_, &measurement| measurement,
1376            )
1377            .expect("open the original basis");
1378        engine
1379            .append_group_allowing_stabilizer_transition(
1380                &[OffsetFlows::new(&consumers, IVec2::ZERO)],
1381                |_, &measurement| measurement,
1382            )
1383            .expect("consume the equivalent basis");
1384
1385        let mut completed = engine.finish().expect("the rebased seam closes");
1386        completed.sort_by_key(|flow| flow.measurements[0]);
1387        assert_eq!(completed[0].measurements.as_slice(), [2, 0, 1]);
1388        assert_eq!(completed[1].measurements.as_slice(), [3, 0]);
1389    }
1390
1391    #[test]
1392    fn indexed_pivots_match_eager_dependency_masks() {
1393        for seed in 0..64u32 {
1394            let generators = (0..75u32)
1395                .map(|index| {
1396                    let bits = seed
1397                        .wrapping_mul(0x9e37_79b9)
1398                        .wrapping_add(index.wrapping_mul(0x85eb_ca6b));
1399                    pauli_map(
1400                        [
1401                            IVec2::new(i32::MIN, 0),
1402                            IVec2::ZERO,
1403                            IVec2::Y,
1404                            IVec2::X,
1405                            IVec2::new(i32::MAX, 0),
1406                        ]
1407                        .into_iter()
1408                        .enumerate()
1409                        .map(|(site, coord)| {
1410                            (
1411                                coord,
1412                                [Pauli::I, Pauli::X, Pauli::Y, Pauli::Z]
1413                                    [((bits >> (2 * site)) & 3) as usize],
1414                            )
1415                        }),
1416                    )
1417                })
1418                .collect::<Vec<_>>();
1419            let generators: Vec<&PauliMap> = generators.iter().collect();
1420            let actual = pauli_dependencies(&generators)
1421                .into_iter()
1422                .map(|(index, mask)| {
1423                    (
1424                        index,
1425                        (0..generators.len())
1426                            .map(|bit| mask[bit / 64] & (1 << (bit % 64)) != 0)
1427                            .collect::<Vec<_>>(),
1428                    )
1429                })
1430                .collect::<Vec<_>>();
1431            assert_eq!(actual, eager_pauli_dependencies(&generators), "seed {seed}");
1432        }
1433    }
1434
1435    #[test]
1436    fn sparse_commutation_matches_product_phase() {
1437        let maps: Vec<_> = (0..128)
1438            .map(|bits| {
1439                pauli_map(
1440                    [IVec2::new(-2, 1), IVec2::ZERO, IVec2::new(5, -1)]
1441                        .into_iter()
1442                        .enumerate()
1443                        .map(|(index, coord)| {
1444                            (
1445                                coord,
1446                                [Pauli::I, Pauli::X, Pauli::Y, Pauli::Z][(bits >> (2 * index)) & 3],
1447                            )
1448                        }),
1449                )
1450            })
1451            .collect();
1452        for offset in [IVec2::ZERO, IVec2::X, IVec2::new(9, 0)] {
1453            // Three Pauli sites give 64 unique columns; keep 128 rows to cover
1454            // swap-removal across both packed words.
1455            let measured = maps
1456                .iter()
1457                .take(64)
1458                .map(|map| map.translated(offset))
1459                .collect::<Vec<_>>();
1460            let columns = pauli_commutation_columns(maps.iter(), measured.iter());
1461            for (row, a) in maps.iter().enumerate() {
1462                for (column, b) in measured.iter().enumerate() {
1463                    let expected = a.product_phase(b) % 2 == 1;
1464                    assert_eq!(columns[column][row / 64] & (1 << (row % 64)) != 0, expected);
1465                }
1466            }
1467            for removed in [0, 63, 64, 127] {
1468                let mut remaining = maps.clone();
1469                remaining.swap_remove(removed);
1470                let mut shortened = columns.clone();
1471                for column in &mut shortened {
1472                    swap_remove_bit(column, removed, maps.len() - 1);
1473                }
1474                let expected = pauli_commutation_columns(remaining.iter(), measured.iter());
1475                assert_eq!(shortened, expected);
1476            }
1477        }
1478    }
1479
1480    #[test]
1481    fn borrowed_flow_keys_match_translated_maps_and_reject_overflow() {
1482        let local = pauli_map([(IVec2::ZERO, Pauli::X), (IVec2::X, Pauli::Z)]);
1483        let offset = IVec2::new(-7, 13);
1484        let translated = local.translated(offset);
1485        let key = FlowKey::try_new(&local, offset).unwrap();
1486        let placed = FlowKey::try_new(&translated, IVec2::ZERO).unwrap();
1487        let mut keys = FxHashMap::default();
1488        keys.insert(key, 42);
1489        assert_eq!(keys.get(&placed), Some(&42));
1490        assert_ne!(key, FlowKey::try_new(&local, IVec2::ZERO).unwrap());
1491        FlowKey::try_new(&local, IVec2::new(i32::MAX, 0)).unwrap_err();
1492    }
1493
1494    #[test]
1495    fn stabilizer_products_keep_their_sign_through_basis_and_gauge_changes() {
1496        let a = IVec2::ZERO;
1497        let b = IVec2::X;
1498        let pair = |pauli| pauli_map([(a, pauli), (b, pauli)]);
1499        let creators = [
1500            flow(PauliMap::empty(), pair(Pauli::X), Some(0)),
1501            flow(PauliMap::empty(), pair(Pauli::Z), Some(1)),
1502        ];
1503        let measured = pauli_map([(a, Pauli::X), (b, Pauli::Z)]);
1504        let gauge = [
1505            flow(measured.clone(), PauliMap::empty(), Some(2)),
1506            flow(PauliMap::empty(), measured, Some(2)),
1507        ];
1508        let consumers = [flow(pair(Pauli::Y), PauliMap::empty(), Some(3))];
1509        for change_gauge in [false, true] {
1510            let mut engine = FlowEngine::<u32>::new();
1511            engine
1512                .append_group_allowing_dangling(
1513                    &[OffsetFlows::new(&creators, IVec2::ZERO)],
1514                    |_, &m| m,
1515                )
1516                .unwrap();
1517            if change_gauge {
1518                engine
1519                    .append_group_allowing_stabilizer_transition(
1520                        &[OffsetFlows::new(&gauge, IVec2::ZERO)],
1521                        |_, &m| m,
1522                    )
1523                    .unwrap();
1524            }
1525            engine
1526                .append_group_allowing_stabilizer_transition(
1527                    &[OffsetFlows::new(&consumers, IVec2::ZERO)],
1528                    |_, &m| m,
1529                )
1530                .unwrap();
1531            let completed = engine.finish().unwrap();
1532            assert_eq!(completed.len(), 1);
1533            let parity =
1534                DetectorParity::from_measurements(completed[0].measurements.iter().copied())
1535                    .with_sign(completed[0].sign);
1536            assert_eq!(
1537                parity,
1538                DetectorParity::from_measurements([0, 1, 3]).with_sign(true),
1539                "XX * ZZ = -YY, including when their product survives as a latent check"
1540            );
1541        }
1542    }
1543
1544    #[test]
1545    fn a_group_reads_only_boundaries_from_earlier_groups() {
1546        let (a, b) = (x(IVec2::ZERO), x(IVec2::X));
1547        let swap = [
1548            flow(a.clone(), b.clone(), None),
1549            flow(b.clone(), a.clone(), None),
1550        ];
1551        let mut residual = FlowEngine::<u32>::new();
1552        residual
1553            .append_residual_group(&[OffsetFlows::new(&swap, IVec2::ZERO)], |_, &m| m)
1554            .unwrap();
1555        let residuals = residual.into_residual_flows();
1556        assert_eq!(residuals.len(), 2);
1557        assert!(residuals.iter().any(|f| f.start == a && f.end == b));
1558        assert!(residuals.iter().any(|f| f.start == b && f.end == a));
1559
1560        let creators = [
1561            flow(PauliMap::empty(), a.clone(), Some(0)),
1562            flow(PauliMap::empty(), b.clone(), Some(1)),
1563        ];
1564        let consumers = [
1565            flow(a, PauliMap::empty(), Some(2)),
1566            flow(b, PauliMap::empty(), Some(3)),
1567        ];
1568        let mut engine = FlowEngine::<u32>::new();
1569        for group in [&creators, &swap, &consumers] {
1570            engine
1571                .append_group_allowing_dangling(&[OffsetFlows::new(group, IVec2::ZERO)], |_, &m| m)
1572                .unwrap();
1573        }
1574        let completed = engine.finish().unwrap();
1575        assert_eq!(completed[0].measurements.as_slice(), [2, 1]);
1576        assert_eq!(completed[1].measurements.as_slice(), [3, 0]);
1577    }
1578
1579    #[test]
1580    fn boundary_composition_matches_an_exact_generator_basis() {
1581        let a = x(IVec2::new(0, 0));
1582        let b = z(IVec2::new(1, 0));
1583        let creators = [
1584            flow(PauliMap::empty(), a.clone(), Some(0)),
1585            flow(PauliMap::empty(), b.clone(), Some(1)),
1586        ];
1587        let consumers = [
1588            flow(a, PauliMap::empty(), Some(2)),
1589            flow(b, PauliMap::empty(), Some(3)),
1590        ];
1591        let mut engine = FlowEngine::<u32>::new();
1592        engine
1593            .append_group_allowing_dangling(
1594                &[OffsetFlows::new(&creators, IVec2::ZERO)],
1595                |_, &measurement| measurement,
1596            )
1597            .expect("open the basis");
1598        engine
1599            .append_group_allowing_stabilizer_transition(
1600                &[OffsetFlows::new(&consumers, IVec2::ZERO)],
1601                |_, &measurement| measurement,
1602            )
1603            .expect("consume the same basis");
1604
1605        let completed = engine.finish().expect("the seam closes");
1606        assert_eq!(completed[0].measurements.as_slice(), [2, 0]);
1607        assert_eq!(completed[1].measurements.as_slice(), [3, 1]);
1608    }
1609
1610    #[test]
1611    fn gauge_transition_carries_only_commuting_old_products() {
1612        let z0 = z(IVec2::new(0, 0));
1613        let z1 = z(IVec2::new(1, 0));
1614        let joint_x = &x(IVec2::new(0, 0)) ^ &x(IVec2::new(1, 0));
1615        let old_creators = [
1616            flow(PauliMap::empty(), z0.clone(), Some(0)),
1617            flow(PauliMap::empty(), z1.clone(), Some(1)),
1618        ];
1619        let merge = [
1620            flow(joint_x.clone(), PauliMap::empty(), Some(2)),
1621            flow(PauliMap::empty(), joint_x, Some(3)),
1622        ];
1623        let split_left = [flow(z0, PauliMap::empty(), Some(4))];
1624        let split_right = [flow(z1, PauliMap::empty(), Some(5))];
1625
1626        let mut engine = FlowEngine::<u32>::new();
1627        engine
1628            .append_group_allowing_dangling(
1629                &[OffsetFlows::new(&old_creators, IVec2::ZERO)],
1630                |_, &measurement| measurement,
1631            )
1632            .expect("open the old basis");
1633        engine
1634            .append_group_allowing_stabilizer_transition(
1635                &[OffsetFlows::new(&merge, IVec2::ZERO)],
1636                |_, &measurement| measurement,
1637            )
1638            .expect("measure the temporary gauge");
1639        engine
1640            .append_group_allowing_stabilizer_transition(
1641                &[OffsetFlows::new(&split_left, IVec2::ZERO)],
1642                |_, &measurement| measurement,
1643            )
1644            .expect("restore the left half of the old basis");
1645        engine
1646            .append_group_allowing_stabilizer_transition(
1647                &[OffsetFlows::new(&split_right, IVec2::ZERO)],
1648                |_, &measurement| measurement,
1649            )
1650            .expect("restore the right half of the old basis");
1651
1652        let completed = engine.finish().expect("the restored basis closes");
1653        assert_eq!(completed.len(), 1);
1654        let mut measurements = completed[0].measurements.to_vec();
1655        measurements.sort_unstable();
1656        assert_eq!(measurements, [0, 1, 4, 5]);
1657    }
1658
1659    #[test]
1660    fn flow_marker_survives_fusion() {
1661        for marker in [FlowMarker::Discard, FlowMarker::Restart] {
1662            let mut engine = FlowEngine::<u32>::new();
1663            let mut offset = 0u32;
1664            let boundary = x(IVec2::new(0, 0));
1665            let creator = [Flow::new(PauliMap::empty(), boundary.clone())
1666                .with_measurements([0])
1667                .with_marker(marker)];
1668            let consumer = [Flow::new(boundary, PauliMap::empty()).with_measurements([0])];
1669
1670            append_flows(
1671                &mut engine,
1672                &creator,
1673                measurement_to_sequential_mapper(&mut offset),
1674            )
1675            .expect("creator");
1676            append_flows(
1677                &mut engine,
1678                &consumer,
1679                measurement_to_sequential_mapper(&mut offset),
1680            )
1681            .expect("consumer");
1682
1683            let completed = engine.finish().expect("clean finish");
1684            assert_eq!(completed.len(), 1);
1685            assert_eq!(completed[0].marker, marker);
1686        }
1687    }
1688
1689    /// A restarting chain left open at the boundary keeps its marker on the
1690    /// residual, so it lands on the template's `boundary_flows` and the
1691    /// instance-seam pass can route the cross-template parity to a restart.
1692    #[test]
1693    fn restart_marker_survives_residual_extraction() {
1694        let mut engine = FlowEngine::<u32>::new();
1695        let creator = [Flow::new(PauliMap::empty(), z(IVec2::new(0, 0)))
1696            .with_measurements([0])
1697            .with_marker(FlowMarker::Restart)];
1698        engine
1699            .append_residual_group(&[OffsetFlows::new(&creator, IVec2::ZERO)], |_, &m| m)
1700            .expect("creator");
1701
1702        let residual = engine.into_residual_flows();
1703        assert_eq!(residual.len(), 1);
1704        assert_eq!(residual[0].marker, FlowMarker::Restart);
1705    }
1706
1707    #[test]
1708    fn consumer_before_creator_ordering() {
1709        let mut engine = FlowEngine::<u32>::new();
1710        let mut offset = 0u32;
1711
1712        let shared_key = x(IVec2::new(0, 0));
1713        let predecessor = [flow(PauliMap::empty(), shared_key.clone(), Some(0))];
1714        append_flows(
1715            &mut engine,
1716            &predecessor,
1717            measurement_to_sequential_mapper(&mut offset),
1718        )
1719        .expect("predecessor");
1720
1721        let time_reversed_chunk = [
1722            flow(PauliMap::empty(), shared_key.clone(), Some(0)),
1723            flow(shared_key.clone(), PauliMap::empty(), Some(0)),
1724        ];
1725
1726        append_flows(
1727            &mut engine,
1728            &time_reversed_chunk,
1729            measurement_to_sequential_mapper(&mut offset),
1730        )
1731        .expect("time-reversed chunk should succeed when consumers are processed first");
1732
1733        let keys: Vec<_> = engine.open_flows.keys().map(FlowKey::translated).collect();
1734        assert_eq!(keys.len(), 1);
1735        assert_eq!(keys[0], shared_key);
1736        assert_eq!(engine.drain_completed().len(), 1);
1737    }
1738
1739    #[test]
1740    fn flow_errors_cover_unmatched_and_unterminated() {
1741        let mut offset = 0u32;
1742
1743        let mut unmatched = FlowEngine::<u32>::new();
1744        let orphan = [flow(x(IVec2::new(0, 0)), PauliMap::empty(), Some(0))];
1745        let err = append_flows(
1746            &mut unmatched,
1747            &orphan,
1748            measurement_to_sequential_mapper(&mut offset),
1749        )
1750        .expect_err("unmatched input");
1751        assert_eq!(
1752            err,
1753            FlowError::Composition(format!("unmatched flow input {}", orphan[0].start))
1754        );
1755
1756        let mut unterminated = FlowEngine::<u32>::new();
1757        let open_ended = [flow(PauliMap::empty(), z(IVec2::new(2, 0)), Some(0))];
1758        append_flows(
1759            &mut unterminated,
1760            &open_ended,
1761            measurement_to_sequential_mapper(&mut offset),
1762        )
1763        .expect("seed");
1764        let err = unterminated.finish().expect_err("unterminated flow");
1765        assert_eq!(
1766            err,
1767            FlowError::Composition(format!("unterminated flows: {}", open_ended[0].end))
1768        );
1769    }
1770
1771    #[test]
1772    fn duplicate_creators_are_rejected_instead_of_overwriting_parity() {
1773        let boundary = z(IVec2::new(2, 0));
1774        let creators = [
1775            flow(PauliMap::empty(), boundary.clone(), Some(0)),
1776            flow(PauliMap::empty(), boundary.clone(), Some(1)),
1777        ];
1778        let mut engine = FlowEngine::<u32>::new();
1779
1780        let error = append_flows(&mut engine, &creators, |measurement| *measurement)
1781            .expect_err("two creators of one boundary must collide");
1782
1783        assert_eq!(
1784            error,
1785            FlowError::Composition(format!("multiple flows create boundary {boundary}"))
1786        );
1787    }
1788
1789    #[test]
1790    fn loop_state_id_exhaustion_is_an_error() {
1791        let boundaries = [x(IVec2::ZERO), x(IVec2::X)];
1792        let creator = [
1793            flow(PauliMap::empty(), boundaries[0].clone(), Some(7)),
1794            flow(PauliMap::empty(), boundaries[1].clone(), Some(8)),
1795        ];
1796        let consumer = [
1797            flow(boundaries[0].clone(), PauliMap::empty(), None),
1798            flow(boundaries[1].clone(), PauliMap::empty(), None),
1799        ];
1800        let mut engine = FlowEngine::<DetectorTerm<u32>>::new();
1801        engine
1802            .append_group_allowing_dangling(
1803                &[OffsetFlows::new(&creator, IVec2::ZERO)],
1804                |_, &measurement| DetectorTerm::Measurement(measurement),
1805            )
1806            .expect("creator");
1807        let mut next_loop_state = u32::MAX - 1;
1808
1809        let error = engine
1810            .start_loop_detector_states(
1811                &[OffsetFlows::new(&consumer, IVec2::ZERO)],
1812                &mut next_loop_state,
1813            )
1814            .expect_err("loop state id space is exhausted");
1815
1816        assert_eq!(
1817            error,
1818            FlowError::Composition("loop detector state id space exhausted".into())
1819        );
1820        assert_eq!(next_loop_state, u32::MAX - 1);
1821        assert!(engine.open_flows.values().all(|flow| {
1822            matches!(
1823                flow.measurements.as_slice(),
1824                [DetectorTerm::Measurement(7 | 8)]
1825            )
1826        }));
1827    }
1828
1829    #[test]
1830    fn loop_state_start_rejects_coordinate_overflow_before_mutating() {
1831        let coordinate = IVec2::new(i32::MAX, 0);
1832        let offset = IVec2::X;
1833        let creator = [flow(PauliMap::empty(), x(IVec2::new(i32::MIN, 0)), Some(7))];
1834        let consumer = [flow(x(coordinate), PauliMap::empty(), None)];
1835        let mut engine = FlowEngine::<DetectorTerm<u32>>::new();
1836        engine
1837            .append_group_allowing_dangling(
1838                &[OffsetFlows::new(&creator, IVec2::ZERO)],
1839                |_, &measurement| DetectorTerm::Measurement(measurement),
1840            )
1841            .expect("creator");
1842        let mut next_loop_state = 0;
1843
1844        let error = engine
1845            .start_loop_detector_states(
1846                &[OffsetFlows::new(&consumer, offset)],
1847                &mut next_loop_state,
1848            )
1849            .expect_err("overflowing loop boundary");
1850
1851        assert_eq!(
1852            error,
1853            FlowError::CoordinateOverflow(crate::CoordinateOverflowError { coordinate, offset })
1854        );
1855        assert_eq!(next_loop_state, 0);
1856        assert!(matches!(
1857            engine
1858                .open_flows
1859                .values()
1860                .next()
1861                .unwrap()
1862                .measurements
1863                .as_slice(),
1864            [DetectorTerm::Measurement(7)]
1865        ));
1866    }
1867
1868    #[test]
1869    fn loop_state_preflight_counts_only_matching_flows() {
1870        let boundary = x(IVec2::ZERO);
1871        let creator = [flow(PauliMap::empty(), boundary.clone(), Some(7))];
1872        let consumers = [
1873            flow(x(IVec2::X), PauliMap::empty(), None),
1874            flow(boundary, PauliMap::empty(), None),
1875        ];
1876        let mut engine = FlowEngine::<DetectorTerm<u32>>::new();
1877        engine
1878            .append_group_allowing_dangling(
1879                &[OffsetFlows::new(&creator, IVec2::ZERO)],
1880                |_, &measurement| DetectorTerm::Measurement(measurement),
1881            )
1882            .expect("creator");
1883        let mut next_loop_state = u32::MAX - 1;
1884
1885        let pending = engine
1886            .start_loop_detector_states(
1887                &[OffsetFlows::new(&consumers, IVec2::ZERO)],
1888                &mut next_loop_state,
1889            )
1890            .expect("only the matching flow needs a state");
1891
1892        assert_eq!(pending.loop_states.len(), 1);
1893        assert_eq!(next_loop_state, u32::MAX);
1894    }
1895
1896    #[test]
1897    fn drain_completed_allows_open_boundaries() {
1898        let mut engine = FlowEngine::<u32>::new();
1899        let mut offset = 0u32;
1900
1901        let chunk = [
1902            flow(PauliMap::empty(), PauliMap::empty(), Some(0)),
1903            flow(PauliMap::empty(), x(IVec2::new(0, 0)), Some(0)),
1904        ];
1905
1906        append_flows(
1907            &mut engine,
1908            &chunk,
1909            measurement_to_sequential_mapper(&mut offset),
1910        )
1911        .expect("chunk");
1912
1913        assert_eq!(engine.drain_completed().len(), 1);
1914    }
1915
1916    #[test]
1917    fn residual_extraction_fuses_a_chain_across_chunks() {
1918        // ∅→K (chunk 0) then K→L (chunk 1): residual mode threads the chain as
1919        // it composes, so the residual is one fused ∅→L carrying both
1920        // measurements.
1921        let mut engine = FlowEngine::<u32>::new();
1922        let creator = [flow(PauliMap::empty(), z(IVec2::new(0, 0)), Some(0))];
1923        let mover = [flow(z(IVec2::new(0, 0)), z(IVec2::new(1, 0)), Some(1))];
1924        engine
1925            .append_residual_group(&[OffsetFlows::new(&creator, IVec2::ZERO)], |_, &m| m)
1926            .expect("creator");
1927        engine
1928            .append_residual_group(&[OffsetFlows::new(&mover, IVec2::ZERO)], |_, &m| m)
1929            .expect("mover");
1930
1931        let residual = engine.into_residual_flows();
1932        assert_eq!(residual.len(), 1, "the chain fuses to a single flow");
1933        assert!(residual[0].start.is_empty());
1934        assert_eq!(residual[0].end, z(IVec2::new(1, 0)));
1935        let mut measurements = residual[0].measurements.to_vec();
1936        measurements.sort_unstable();
1937        assert_eq!(measurements, [0, 1]);
1938    }
1939
1940    #[test]
1941    fn residual_extraction_keeps_opposite_boundary_faces_separate() {
1942        // One chunk with a bottom consumer K→∅ and a top creator ∅→K. They share
1943        // the key K but face opposite neighbors; consumers are matched before
1944        // creators, so the consumer never matches this chunk's own creator and
1945        // the two stay distinct residual flows.
1946        let mut engine = FlowEngine::<u32>::new();
1947        let faces = [
1948            flow(z(IVec2::new(0, 0)), PauliMap::empty(), Some(0)),
1949            flow(PauliMap::empty(), z(IVec2::new(0, 0)), Some(1)),
1950        ];
1951        engine
1952            .append_residual_group(&[OffsetFlows::new(&faces, IVec2::ZERO)], |_, &m| m)
1953            .expect("faces");
1954
1955        let mut residual = engine.into_residual_flows();
1956        residual.sort_by_key(|flow| flow.measurements.first().copied());
1957        assert_eq!(residual.len(), 2, "opposite faces are not fused");
1958        // Bottom consumer K→∅ closed against a boundary input below.
1959        assert_eq!(residual[0].start, z(IVec2::new(0, 0)));
1960        assert!(residual[0].end.is_empty());
1961        // Top creator ∅→K stays open for the neighbor above.
1962        assert!(residual[1].start.is_empty());
1963        assert_eq!(residual[1].end, z(IVec2::new(0, 0)));
1964    }
1965}