fusion_blossom/
dual_module.rs

1//! Dual Module
2//!
3//! Generics for dual modules, defining the necessary interfaces for a dual module
4//!
5
6#![cfg_attr(feature = "unsafe_pointer", allow(dropping_references))]
7
8use core::cmp::Ordering;
9use std::collections::{BTreeMap, HashSet};
10use std::num::NonZeroUsize;
11#[cfg(not(feature = "dangerous_pointer"))]
12use std::sync::Arc;
13
14use nonzero::nonzero as nz;
15
16use crate::derivative::Derivative;
17
18use super::pointers::*;
19use super::util::*;
20use super::visualize::*;
21
22/// A dual node is either a blossom or a vertex
23#[derive(Derivative, Clone)]
24#[derivative(Debug)]
25pub enum DualNodeClass {
26    Blossom {
27        nodes_circle: Vec<DualNodeWeak>,
28        touching_children: Vec<(DualNodeWeak, DualNodeWeak)>,
29    },
30    DefectVertex {
31        defect_index: VertexIndex,
32    },
33}
34
35impl DualNodeClass {
36    pub fn is_blossom(&self) -> bool {
37        matches!(self, Self::Blossom { .. })
38    }
39}
40
41/// Three possible states: Grow (+1), Stay (+0), Shrink (-1)
42#[derive(Derivative, PartialEq, Eq, Clone, Copy)]
43#[derivative(Debug)]
44pub enum DualNodeGrowState {
45    Grow,
46    Stay,
47    Shrink,
48}
49
50impl DualNodeGrowState {
51    pub fn is_against(&self, other: &Self) -> bool {
52        matches!(
53            (self, other),
54            (Self::Grow, Self::Grow | Self::Stay) | (Self::Stay, Self::Grow)
55        )
56    }
57}
58
59/// synchronize request on vertices, when a vertex is mirrored
60#[derive(Derivative)]
61#[derivative(Debug)]
62pub struct SyncRequest {
63    /// the unit that owns this vertex
64    pub mirror_unit_weak: PartitionUnitWeak,
65    /// the vertex index to be synchronized
66    pub vertex_index: VertexIndex,
67    /// propagated dual node index and the dual variable of the propagated dual node;
68    /// this field is necessary to differentiate between normal shrink and the one that needs to report VertexShrinkStop event, when the syndrome is on the interface;
69    /// it also includes the representative vertex of the dual node, so that parents can keep track of whether it should be elevated
70    pub propagated_dual_node: Option<(DualNodeWeak, Weight, VertexIndex)>,
71    /// propagated grandson node: must be a syndrome node
72    pub propagated_grandson_dual_node: Option<(DualNodeWeak, Weight, VertexIndex)>,
73}
74
75impl SyncRequest {
76    /// update all the interface nodes to be up-to-date, only necessary when there are fusion
77    pub fn update(&self) {
78        if let Some((weak, ..)) = &self.propagated_dual_node {
79            weak.upgrade_force().update();
80        }
81        if let Some((weak, ..)) = &self.propagated_grandson_dual_node {
82            weak.upgrade_force().update();
83        }
84    }
85}
86
87/// gives the maximum absolute length to grow, if not possible, give the reason;
88/// note that strong reference is stored in `MaxUpdateLength` so dropping these temporary messages are necessary to avoid memory leakage;
89/// the strong reference is required when multiple `BlossomNeedExpand` event is reported in different partitions and sorting them requires a reference
90#[derive(Derivative, PartialEq, Eq, Clone)]
91#[derivative(Debug)]
92pub enum MaxUpdateLength {
93    /// non-zero maximum update length, has_empty_boundary_node (useful in fusion)
94    NonZeroGrow((Weight, bool)),
95    /// conflicting growth
96    Conflicting((DualNodePtr, DualNodePtr), (DualNodePtr, DualNodePtr)), // (node_1, touching_1), (node_2, touching_2)
97    /// conflicting growth because of touching virtual node
98    TouchingVirtual((DualNodePtr, DualNodePtr), (VertexIndex, bool)), // (node, touching), (virtual_vertex, is_mirror)
99    /// blossom hitting 0 dual variable while shrinking
100    BlossomNeedExpand(DualNodePtr),
101    /// node hitting 0 dual variable while shrinking: note that this should have the lowest priority, normally it won't show up in a normal primal module;
102    /// in case that the dual module is partitioned and nobody can report this conflicting event, one needs to embed the potential conflicts using the second
103    /// argument so that dual module can gather two `VertexShrinkStop` events to form a single `Conflicting` event
104    VertexShrinkStop((DualNodePtr, Option<(DualNodePtr, DualNodePtr)>)),
105}
106
107cfg_if::cfg_if! {
108    if #[cfg(feature="ordered_conflicts")] {
109        use std::collections::BinaryHeap;
110        pub type ConflictList = BinaryHeap<MaxUpdateLength>;
111    } else {
112        pub type ConflictList = Vec<MaxUpdateLength>;
113    }
114}
115
116#[derive(Derivative, Clone)]
117#[derivative(Debug)]
118pub enum GroupMaxUpdateLength {
119    /// non-zero maximum update length, has_empty_boundary_node (useful in fusion)
120    NonZeroGrow((Weight, bool)),
121    /// conflicting reasons and pending VertexShrinkStop events (empty in a single serial dual module)
122    Conflicts((ConflictList, BTreeMap<VertexIndex, MaxUpdateLength>)),
123}
124
125impl Default for GroupMaxUpdateLength {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl GroupMaxUpdateLength {
132    pub fn new() -> Self {
133        Self::NonZeroGrow((Weight::MAX, false))
134    }
135
136    /// update all the interface nodes to be up-to-date, only necessary in existence of fusion
137    #[inline(never)]
138    pub fn update(&self) {
139        if let Self::Conflicts((list, pending_stops)) = &self {
140            for max_update_length in list.iter() {
141                max_update_length.update();
142            }
143            for (_, max_update_length) in pending_stops.iter() {
144                max_update_length.update();
145            }
146        }
147    }
148
149    pub fn add_pending_stop(
150        list: &mut ConflictList,
151        pending_stops: &mut BTreeMap<VertexIndex, MaxUpdateLength>,
152        max_update_length: MaxUpdateLength,
153    ) {
154        if let Some(dual_node_ptr) = max_update_length.get_vertex_shrink_stop() {
155            let vertex_index = dual_node_ptr.get_representative_vertex();
156            if let Some(existing_length) = pending_stops.get(&vertex_index) {
157                if let MaxUpdateLength::VertexShrinkStop((_, Some(weak_pair))) = &max_update_length {
158                    // otherwise don't update
159                    if let MaxUpdateLength::VertexShrinkStop((_, Some(existing_weak_pair))) = existing_length {
160                        if weak_pair.0 != existing_weak_pair.0 {
161                            // two such conflicts form a Conflicting event
162                            list.push(MaxUpdateLength::Conflicting(weak_pair.clone(), existing_weak_pair.clone()));
163                            pending_stops.remove(&vertex_index);
164                        }
165                    } else {
166                        pending_stops.insert(vertex_index, max_update_length.clone());
167                        // update the entry
168                    }
169                }
170            } else {
171                pending_stops.insert(vertex_index, max_update_length);
172            }
173        } else {
174            list.push(max_update_length);
175        }
176    }
177
178    pub fn add(&mut self, max_update_length: MaxUpdateLength) {
179        match self {
180            Self::NonZeroGrow((current_length, current_has_empty_boundary_node)) => {
181                if let MaxUpdateLength::NonZeroGrow((length, has_empty_boundary_node)) = max_update_length {
182                    *current_length = std::cmp::min(*current_length, length);
183                    *current_has_empty_boundary_node |= has_empty_boundary_node;
184                // or
185                } else {
186                    let mut list = ConflictList::new();
187                    let mut pending_stops = BTreeMap::new();
188                    if let Some(dual_node_ptr) = max_update_length.get_vertex_shrink_stop() {
189                        let vertex_index = dual_node_ptr.get_representative_vertex();
190                        pending_stops.insert(vertex_index, max_update_length);
191                    } else {
192                        list.push(max_update_length);
193                    }
194                    *self = Self::Conflicts((list, pending_stops));
195                }
196            }
197            Self::Conflicts((list, pending_stops)) => {
198                // only add conflicts, not NonZeroGrow
199                if !matches!(max_update_length, MaxUpdateLength::NonZeroGrow(_)) {
200                    Self::add_pending_stop(list, pending_stops, max_update_length);
201                }
202            }
203        }
204    }
205
206    pub fn extend(&mut self, other: Self) {
207        if other.is_empty() {
208            return; // do nothing
209        }
210        match self {
211            Self::NonZeroGrow(current_length) => match other {
212                Self::NonZeroGrow(length) => {
213                    *current_length = std::cmp::min(*current_length, length);
214                }
215                Self::Conflicts((mut other_list, mut other_pending_stops)) => {
216                    let mut list = ConflictList::new();
217                    let mut pending_stops = BTreeMap::new();
218                    std::mem::swap(&mut list, &mut other_list);
219                    std::mem::swap(&mut pending_stops, &mut other_pending_stops);
220                    *self = Self::Conflicts((list, pending_stops));
221                }
222            },
223            Self::Conflicts((list, pending_stops)) => {
224                if let Self::Conflicts((other_list, other_pending_stops)) = other {
225                    list.extend(other_list);
226                    for (_, max_update_length) in other_pending_stops.into_iter() {
227                        Self::add_pending_stop(list, pending_stops, max_update_length);
228                    }
229                } // only add conflicts, not NonZeroGrow
230            }
231        }
232    }
233
234    pub fn is_empty(&self) -> bool {
235        matches!(self, Self::NonZeroGrow((Weight::MAX, _))) // if `has_empty_boundary_node`, then it's not considered empty
236    }
237
238    pub fn is_active(&self) -> bool {
239        !matches!(self, Self::NonZeroGrow((Weight::MAX, false))) // if `has_empty_boundary_node`, then it's still considered active
240    }
241
242    pub fn get_none_zero_growth(&self) -> Option<Weight> {
243        match self {
244            Self::NonZeroGrow((length, _has_empty_boundary_node)) => {
245                debug_assert!(
246                    *length != Weight::MAX,
247                    "please call GroupMaxUpdateLength::is_empty to check if this group is empty"
248                );
249                Some(*length)
250            }
251            _ => None,
252        }
253    }
254
255    pub fn pop(&mut self) -> Option<MaxUpdateLength> {
256        match self {
257            Self::NonZeroGrow(_) => {
258                panic!("please call GroupMaxUpdateLength::get_none_zero_growth to check if this group is none_zero_growth");
259            }
260            Self::Conflicts((list, pending_stops)) => {
261                list.pop().or(if let Some(key) = pending_stops.keys().next().cloned() {
262                    pending_stops.remove(&key)
263                } else {
264                    None
265                })
266            }
267        }
268    }
269
270    pub fn peek(&self) -> Option<&MaxUpdateLength> {
271        match self {
272            Self::NonZeroGrow(_) => {
273                panic!("please call GroupMaxUpdateLength::get_none_zero_growth to check if this group is none_zero_growth");
274            }
275            Self::Conflicts((list, pending_stops)) => {
276                cfg_if::cfg_if! {
277                    if #[cfg(feature="ordered_conflicts")] {
278                        let peek_element = list.peek();
279                    } else {
280                        let peek_element = list.last();
281                    }
282                }
283                peek_element.or(if pending_stops.is_empty() {
284                    None
285                } else {
286                    pending_stops.values().next()
287                })
288            }
289        }
290    }
291}
292
293/// A dual node corresponds to either a vertex or a blossom (on which the dual variables are defined)
294#[derive(Derivative, Clone)]
295#[derivative(Debug)]
296pub struct DualNode {
297    /// the index of this dual node, helps to locate internal details of this dual node
298    pub index: NodeIndex,
299    /// the class of this dual node
300    pub class: DualNodeClass,
301    /// whether it grows, stays or shrinks
302    pub grow_state: DualNodeGrowState,
303    /// parent blossom: when parent exists, grow_state should be [`DualNodeGrowState::Stay`]
304    pub parent_blossom: Option<DualNodeWeak>,
305    /// information used to compute dual variable of this node: (last dual variable, last global progress)
306    pub dual_variable_cache: (Weight, Weight),
307    /// belonging of the dual module interface; a dual node is never standalone
308    pub belonging: DualModuleInterfaceWeak,
309    /// how many defect vertices in this dual node
310    pub defect_size: NonZeroUsize,
311}
312
313impl DualNode {
314    /// get the current dual variable of a node
315    pub fn get_dual_variable(&self, interface: &DualModuleInterface) -> Weight {
316        let (last_dual_variable, last_global_progress) = self.dual_variable_cache;
317        match self.grow_state {
318            DualNodeGrowState::Grow => last_dual_variable + (interface.dual_variable_global_progress - last_global_progress),
319            DualNodeGrowState::Stay => last_dual_variable,
320            DualNodeGrowState::Shrink => {
321                last_dual_variable - (interface.dual_variable_global_progress - last_global_progress)
322            }
323        }
324    }
325}
326
327// should not use dangerous pointer because expanding a blossom will leave a weak pointer invalid
328pub type DualNodePtr = ArcManualSafeLock<DualNode>;
329pub type DualNodeWeak = WeakManualSafeLock<DualNode>;
330
331impl Ord for DualNodePtr {
332    // a consistent compare (during a single program)
333    fn cmp(&self, other: &Self) -> Ordering {
334        cfg_if::cfg_if! {
335            if #[cfg(feature="dangerous_pointer")] {
336                let node1 = self.read_recursive();
337                let node2 = other.read_recursive();
338                node1.index.cmp(&node2.index)
339            } else {
340                if false {  // faster way: compare pointer address, just to have a consistent order between pointers
341                    let ptr1 = Arc::as_ptr(self.ptr());
342                    let ptr2 = Arc::as_ptr(other.ptr());
343                    // https://doc.rust-lang.org/reference/types/pointer.html
344                    // "When comparing raw pointers they are compared by their address, rather than by what they point to."
345                    ptr1.cmp(&ptr2)
346                } else {
347                    let node1 = self.read_recursive();
348                    let node2 = other.read_recursive();
349                    node1.index.cmp(&node2.index)
350                }
351            }
352        }
353    }
354}
355
356impl PartialOrd for DualNodePtr {
357    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
358        Some(self.cmp(other))
359    }
360}
361
362impl std::fmt::Debug for DualNodePtr {
363    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
364        self.update(); // to make sure index is up-to-date
365        let dual_node = self.read_recursive(); // reading index is consistent
366        write!(f, "{}", dual_node.index)
367    }
368}
369
370impl std::fmt::Debug for DualNodeWeak {
371    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
372        self.upgrade_force().fmt(f)
373    }
374}
375
376impl DualNodePtr {
377    /// when fused, dual node may be outdated; refresh here
378    #[allow(clippy::needless_borrow)]
379    pub fn update(&self) -> &Self {
380        let mut current_belonging = self.read_recursive().belonging.upgrade_force();
381        let mut bias = 0;
382        let mut node = self.write();
383        while current_belonging.read_recursive().parent.is_some() {
384            let belonging_interface = current_belonging.read_recursive();
385            bias += belonging_interface.index_bias;
386            let new_current_belonging = belonging_interface.parent.clone().unwrap().upgrade_force();
387            let dual_variable = node.get_dual_variable(&belonging_interface); // aggregate the dual variable
388            node.dual_variable_cache = (dual_variable, 0); // this will be the state when joining the new interface
389            drop(belonging_interface);
390            current_belonging = new_current_belonging;
391        }
392        node.belonging = current_belonging.downgrade();
393        node.index += bias;
394        self
395    }
396
397    pub fn updated_index(&self) -> NodeIndex {
398        self.update();
399        self.read_recursive().index
400    }
401
402    /// helper function to set grow state with sanity check
403    fn set_grow_state(&self, grow_state: DualNodeGrowState) {
404        let mut dual_node = self.write();
405        debug_assert!(
406            dual_node.parent_blossom.is_none(),
407            "setting node grow state inside a blossom forbidden"
408        );
409        dual_node.grow_state = grow_state;
410    }
411
412    /// get parent blossom recursively
413    pub fn get_ancestor_blossom(&self) -> DualNodePtr {
414        let dual_node = self.read_recursive();
415        match &dual_node.parent_blossom {
416            Some(ptr) => ptr.upgrade_force().get_ancestor_blossom(),
417            None => self.clone(),
418        }
419    }
420
421    /// get the parent blossom before the most parent one, useful when expanding a blossom
422    pub fn get_secondary_ancestor_blossom(&self) -> DualNodePtr {
423        let mut secondary_ancestor = self.clone();
424        let mut ancestor = self
425            .read_recursive()
426            .parent_blossom
427            .as_ref()
428            .expect("secondary ancestor does not exist")
429            .upgrade_force();
430        loop {
431            let dual_node = ancestor.read_recursive();
432            let new_ancestor = match &dual_node.parent_blossom {
433                Some(weak) => weak.upgrade_force(),
434                None => {
435                    return secondary_ancestor;
436                }
437            };
438            drop(dual_node);
439            secondary_ancestor = ancestor.clone();
440            ancestor = new_ancestor;
441        }
442    }
443
444    fn __get_all_vertices(&self, pending_vec: &mut Vec<VertexIndex>) {
445        let dual_node = self.read_recursive();
446        match &dual_node.class {
447            DualNodeClass::Blossom { nodes_circle, .. } => {
448                for node_ptr in nodes_circle.iter() {
449                    node_ptr.upgrade_force().__get_all_vertices(pending_vec);
450                }
451            }
452            DualNodeClass::DefectVertex { defect_index } => {
453                pending_vec.push(*defect_index);
454            }
455        };
456    }
457
458    /// find all vertices that belongs to the dual node, i.e. any vertices inside a blossom
459    pub fn get_all_vertices(&self) -> Vec<VertexIndex> {
460        let mut pending_vec = vec![];
461        self.__get_all_vertices(&mut pending_vec);
462        pending_vec
463    }
464
465    /// find a representative vertex
466    pub fn get_representative_vertex(&self) -> VertexIndex {
467        let dual_node = self.read_recursive();
468        match &dual_node.class {
469            DualNodeClass::Blossom { nodes_circle, .. } => nodes_circle[0].upgrade_force().get_representative_vertex(),
470            DualNodeClass::DefectVertex { defect_index } => *defect_index,
471        }
472    }
473}
474
475/// a sharable array of dual nodes, supporting dynamic partitioning;
476/// note that a node can be destructed and we do not reuse its index, leaving a blank space
477#[derive(Derivative)]
478#[derivative(Debug)]
479pub struct DualModuleInterface {
480    /// unit index of this interface, default to 0
481    pub unit_index: usize,
482    /// all the dual node that can be used to control a concrete dual module implementation
483    pub nodes: Vec<Option<DualNodePtr>>,
484    /// current nodes length, to enable constant-time clear operation
485    pub nodes_length: usize,
486    /// allow pointer reuse will reduce the time of reallocation, but it's unsafe if not owning it;
487    /// this will be automatically disabled when [`DualModuleInterface::fuse`] is called;
488    /// if an interface is involved in a fusion operation (whether as parent or child), it will be set.
489    pub is_fusion: bool,
490    /// record the total growing nodes, should be non-negative in a normal running algorithm
491    pub sum_grow_speed: Weight,
492    /// record the total sum of dual variables
493    pub sum_dual_variables: Weight,
494    /// debug mode: only resolve one conflict each time
495    pub debug_print_actions: bool,
496    /// information used to compute dual variable of this node: (last dual variable, last global progress)
497    dual_variable_global_progress: Weight,
498    /// the parent of this interface, when fused
499    pub parent: Option<DualModuleInterfaceWeak>,
500    /// when fused, this will indicate the relative bias given by the parent
501    pub index_bias: NodeIndex,
502    /// the two children of this interface, when fused; following the length of this child,
503    /// given that fused children interface will not have new nodes anymore
504    pub children: Option<((DualModuleInterfaceWeak, NodeIndex), (DualModuleInterfaceWeak, NodeIndex))>,
505}
506
507pub type DualModuleInterfacePtr = ArcManualSafeLock<DualModuleInterface>;
508pub type DualModuleInterfaceWeak = WeakManualSafeLock<DualModuleInterface>;
509
510impl std::fmt::Debug for DualModuleInterfacePtr {
511    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
512        let interface = self.read_recursive();
513        write!(f, "{}", interface.unit_index)
514    }
515}
516
517impl std::fmt::Debug for DualModuleInterfaceWeak {
518    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
519        self.upgrade_force().fmt(f)
520    }
521}
522
523/// common trait that must be implemented for each implementation of dual module
524pub trait DualModuleImpl {
525    /// create a new dual module with empty syndrome
526    fn new_empty(initializer: &SolverInitializer) -> Self;
527
528    /// clear all growth and existing dual nodes, prepared for the next decoding
529    fn clear(&mut self);
530
531    /// add corresponding dual node
532    fn add_dual_node(&mut self, dual_node_ptr: &DualNodePtr);
533
534    #[inline(always)]
535    /// helper function to specifically add a syndrome node
536    fn add_defect_node(&mut self, dual_node_ptr: &DualNodePtr) {
537        debug_assert!(
538            {
539                let node = dual_node_ptr.read_recursive();
540                matches!(node.class, DualNodeClass::DefectVertex { .. })
541            },
542            "node class mismatch"
543        );
544        self.add_dual_node(dual_node_ptr)
545    }
546
547    #[inline(always)]
548    /// helper function to specifically add a blossom node
549    fn add_blossom(&mut self, dual_node_ptr: &DualNodePtr) {
550        debug_assert!(
551            {
552                let node = dual_node_ptr.read_recursive();
553                matches!(node.class, DualNodeClass::Blossom { .. })
554            },
555            "node class mismatch"
556        );
557        self.add_dual_node(dual_node_ptr)
558    }
559
560    /// remove a blossom, note that this dual node ptr is already expanded from the root: normally you only need to remove this blossom;
561    /// when force flag is set, remove blossom even if its dual variable is not 0: this action cannot be undone
562    fn remove_blossom(&mut self, dual_node_ptr: DualNodePtr);
563
564    /// update grow state
565    fn set_grow_state(&mut self, dual_node_ptr: &DualNodePtr, grow_state: DualNodeGrowState);
566
567    /// An optional function that helps to break down the implementation of [`DualModuleImpl::compute_maximum_update_length`]
568    /// check the maximum length to grow (shrink) specific dual node, if length is 0, give the reason of why it cannot further grow (shrink).
569    /// if `is_grow` is false, return `length` <= 0, in any case |`length`| is maximized so that at least one edge becomes fully grown or fully not-grown.
570    /// if `simultaneous_update` is true, also check for the peer node according to [`DualNode::grow_state`].
571    fn compute_maximum_update_length_dual_node(
572        &mut self,
573        _dual_node_ptr: &DualNodePtr,
574        _is_grow: bool,
575        _simultaneous_update: bool,
576    ) -> MaxUpdateLength {
577        panic!("the dual module implementation doesn't support this function, please use another dual module")
578    }
579
580    /// check the maximum length to grow (shrink) for all nodes, return a list of conflicting reason and a single number indicating the maximum length to grow:
581    /// this number will be 0 if any conflicting reason presents
582    fn compute_maximum_update_length(&mut self) -> GroupMaxUpdateLength;
583
584    /// An optional function that can manipulate individual dual node, not necessarily supported by all implementations
585    fn grow_dual_node(&mut self, _dual_node_ptr: &DualNodePtr, _length: Weight) {
586        panic!("the dual module implementation doesn't support this function, please use another dual module")
587    }
588
589    /// grow a specific length globally, length must be positive.
590    /// note that reversing the process is possible, but not recommended: to do that, reverse the state of each dual node, Grow->Shrink, Shrink->Grow
591    fn grow(&mut self, length: Weight);
592
593    /// optional support for edge modifier. for example, erasure errors temporarily set some edges to 0 weight.
594    /// When it clears, those edges must be reverted back to the original weight
595    fn load_edge_modifier(&mut self, _edge_modifier: &[(EdgeIndex, Weight)]) {
596        unimplemented!(
597            "load_edge_modifier is an optional interface, and the current dual module implementation doesn't support it"
598        );
599    }
600
601    /// an erasure error means this edge is totally uncertain: p=0.5, so new weight = ln((1-p)/p) = 0
602    fn load_erasures(&mut self, erasures: &[EdgeIndex]) {
603        let edge_modifier: Vec<_> = erasures.iter().map(|edge_index| (*edge_index, 0)).collect();
604        self.load_edge_modifier(&edge_modifier);
605    }
606
607    fn load_dynamic_weights(&mut self, dynamic_weights: &[(EdgeIndex, Weight)]) {
608        let edge_modifier = dynamic_weights.to_vec();
609        self.load_edge_modifier(&edge_modifier);
610    }
611
612    /// prepare a list of nodes as shrinking state; useful in creating a blossom
613    fn prepare_nodes_shrink(&mut self, _nodes_circle: &[DualNodePtr]) -> &mut Vec<SyncRequest> {
614        panic!("the dual module implementation doesn't support this function, please use another dual module")
615    }
616
617    /// performance profiler report
618    fn generate_profiler_report(&self) -> serde_json::Value {
619        json!({})
620    }
621
622    /*
623     * the following apis are only required when this dual module can be used as a partitioned one
624     */
625
626    /// create a partitioned dual module (hosting only a subgraph and subset of dual nodes) to be used in the parallel dual module
627    fn new_partitioned(_partitioned_initializer: &PartitionedSolverInitializer) -> Self
628    where
629        Self: std::marker::Sized,
630    {
631        panic!("the dual module implementation doesn't support this function, please use another dual module")
632    }
633
634    /// prepare the growing or shrinking state of all nodes and return a list of sync requests in case of mirrored vertices are changed
635    fn prepare_all(&mut self) -> &mut Vec<SyncRequest> {
636        panic!("the dual module implementation doesn't support this function, please use another dual module")
637    }
638
639    /// execute a synchronize event by updating the state of a vertex and also update the internal dual node accordingly
640    fn execute_sync_event(&mut self, _sync_event: &SyncRequest) {
641        panic!("the dual module implementation doesn't support this function, please use another dual module")
642    }
643
644    /// judge whether the current module hosts the dual node
645    fn contains_dual_node(&self, _dual_node_ptr: &DualNodePtr) -> bool {
646        panic!("the dual module implementation doesn't support this function, please use another dual module")
647    }
648
649    /// judge whether the current module hosts any of these dual node
650    fn contains_dual_nodes_any(&self, dual_node_ptrs: &[DualNodePtr]) -> bool {
651        for dual_node_ptr in dual_node_ptrs.iter() {
652            if self.contains_dual_node(dual_node_ptr) {
653                return true;
654            }
655        }
656        false
657    }
658
659    /// judge whether the current module hosts a vertex
660    fn contains_vertex(&self, _vertex_index: VertexIndex) -> bool {
661        panic!("the dual module implementation doesn't support this function, please use another dual module")
662    }
663
664    /// bias the global dual node indices
665    fn bias_dual_node_index(&mut self, _bias: NodeIndex) {
666        panic!("the dual module implementation doesn't support this function, please use another dual module")
667    }
668}
669
670/// this dual module is a parallel version that hosts many partitioned ones
671pub trait DualModuleParallelImpl {
672    type UnitType: DualModuleImpl + Send + Sync;
673
674    fn get_unit(&self, unit_index: usize) -> ArcManualSafeLock<Self::UnitType>;
675}
676
677impl FusionVisualizer for DualModuleInterfacePtr {
678    fn snapshot(&self, abbrev: bool) -> serde_json::Value {
679        // do the sanity check first before taking snapshot
680        let flattened_nodes = self.sanity_check().unwrap();
681        let interface = self.read_recursive();
682        let mut dual_nodes = Vec::<serde_json::Value>::new();
683        for dual_node_ptr in flattened_nodes.iter() {
684            if let Some(dual_node_ptr) = &dual_node_ptr {
685                let dual_node = dual_node_ptr.read_recursive();
686                dual_nodes.push(json!({
687                    if abbrev { "o" } else { "blossom" }: match &dual_node.class {
688                        DualNodeClass::Blossom { nodes_circle, .. } => Some(nodes_circle.iter().map(|node_ptr|
689                            node_ptr.upgrade_force().read_recursive().index).collect::<Vec<NodeIndex>>()),
690                        _ => None,
691                    },
692                    if abbrev { "t" } else { "touching_children" }: match &dual_node.class {
693                        DualNodeClass::Blossom { touching_children, .. } => Some(touching_children.iter().map(|(node_ptr_1, node_ptr_2)|
694                            (node_ptr_1.upgrade_force().read_recursive().index, node_ptr_2.upgrade_force().read_recursive().index)).collect::<Vec<(NodeIndex, NodeIndex)>>()),
695                        _ => None,
696                    },
697                    if abbrev { "s" } else { "defect_vertex" }: match &dual_node.class {
698                        DualNodeClass::DefectVertex { defect_index } => Some(defect_index),
699                        _ => None,
700                    },
701                    if abbrev { "g" } else { "grow_state" }: match &dual_node.grow_state {
702                        DualNodeGrowState::Grow => "grow",
703                        DualNodeGrowState::Shrink => "shrink",
704                        DualNodeGrowState::Stay => "stay",
705                    },
706                    if abbrev { "u" } else { "unit_growth" }: match &dual_node.grow_state {
707                        DualNodeGrowState::Grow => 1,
708                        DualNodeGrowState::Shrink => -1,
709                        DualNodeGrowState::Stay => 0,
710                    },
711                    if abbrev { "p" } else { "parent_blossom" }: dual_node.parent_blossom.as_ref().map(|weak| weak.upgrade_force().read_recursive().index),
712                }));
713            } else {
714                dual_nodes.push(json!(null));
715            }
716        }
717        json!({
718            "interface": {
719                if abbrev { "s" } else { "sum_grow_speed" }: interface.sum_grow_speed,
720                if abbrev { "d" } else { "sum_dual_variables" }: interface.sum_dual_variables,
721            },
722            "dual_nodes": dual_nodes,
723        })
724    }
725}
726
727impl DualModuleInterface {
728    /// return the count of all nodes including those of the children interfaces
729    pub fn nodes_count(&self) -> NodeNum {
730        let mut count = self.nodes_length as NodeNum;
731        if let Some(((_, left_count), (_, right_count))) = &self.children {
732            count += left_count + right_count;
733        }
734        count
735    }
736
737    /// get node ptr by index; if calling from the ancestor interface, node_index is absolute, otherwise it's relative
738    #[allow(clippy::unnecessary_cast)]
739    pub fn get_node(&self, relative_node_index: NodeIndex) -> Option<DualNodePtr> {
740        debug_assert!(relative_node_index < self.nodes_count(), "cannot find node in this interface");
741        let mut bias = 0;
742        if let Some(((left_weak, left_count), (right_weak, right_count))) = &self.children {
743            if relative_node_index < *left_count {
744                // this node belongs to the left
745                return left_weak.upgrade_force().read_recursive().get_node(relative_node_index);
746            } else if relative_node_index < *left_count + *right_count {
747                // this node belongs to the right
748                return right_weak
749                    .upgrade_force()
750                    .read_recursive()
751                    .get_node(relative_node_index - *left_count);
752            }
753            bias = left_count + right_count;
754        }
755        self.nodes[(relative_node_index - bias) as usize].clone()
756    }
757
758    /// set the corresponding node index to None
759    #[allow(clippy::unnecessary_cast)]
760    pub fn remove_node(&mut self, relative_node_index: NodeIndex) {
761        debug_assert!(relative_node_index < self.nodes_count(), "cannot find node in this interface");
762        let mut bias = 0;
763        if let Some(((left_weak, left_count), (right_weak, right_count))) = &self.children {
764            if relative_node_index < *left_count {
765                // this node belongs to the left
766                left_weak.upgrade_force().write().remove_node(relative_node_index);
767                return;
768            } else if relative_node_index < *left_count + *right_count {
769                // this node belongs to the right
770                right_weak
771                    .upgrade_force()
772                    .write()
773                    .remove_node(relative_node_index - *left_count);
774                return;
775            }
776            bias = left_count + right_count;
777        }
778        self.nodes[(relative_node_index - bias) as usize] = None;
779    }
780}
781
782impl DualModuleInterfacePtr {
783    /// create an empty interface
784    pub fn new_empty() -> Self {
785        Self::new_value(DualModuleInterface {
786            unit_index: 0, // if necessary, manually change it
787            nodes: Vec::new(),
788            nodes_length: 0,
789            is_fusion: false,
790            sum_grow_speed: 0,
791            sum_dual_variables: 0,
792            debug_print_actions: false,
793            dual_variable_global_progress: 0,
794            parent: None,
795            index_bias: 0,
796            children: None,
797        })
798    }
799
800    /// a dual module interface MUST be created given a concrete implementation of the dual module
801    pub fn new_load(syndrome_pattern: &SyndromePattern, dual_module_impl: &mut impl DualModuleImpl) -> Self {
802        let interface_ptr = Self::new_empty();
803        interface_ptr.load(syndrome_pattern, dual_module_impl);
804        interface_ptr
805    }
806
807    pub fn load(&self, syndrome_pattern: &SyndromePattern, dual_module_impl: &mut impl DualModuleImpl) {
808        for vertex_idx in syndrome_pattern.defect_vertices.iter() {
809            self.create_defect_node(*vertex_idx, dual_module_impl);
810        }
811        if !syndrome_pattern.erasures.is_empty() {
812            assert!(
813                syndrome_pattern.dynamic_weights.is_empty(),
814                "erasures and dynamic_weights cannot be provided at the same time"
815            );
816            dual_module_impl.load_erasures(&syndrome_pattern.erasures);
817        }
818        if !syndrome_pattern.dynamic_weights.is_empty() {
819            dual_module_impl.load_dynamic_weights(&syndrome_pattern.dynamic_weights);
820        }
821    }
822
823    /// a constant clear function, without dropping anything;
824    /// this is for consideration of reducing the garbage collection time in the parallel solver,
825    /// by distributing the clear cost into each thread but not the single main thread.
826    pub fn clear(&self) {
827        let mut interface = self.write();
828        interface.nodes_length = 0;
829        interface.sum_grow_speed = 0;
830        interface.sum_dual_variables = 0;
831        interface.dual_variable_global_progress = 0;
832        interface.is_fusion = false;
833        interface.parent = None;
834        interface.index_bias = 0;
835        interface.children = None;
836    }
837
838    /// DFS flatten the nodes
839    pub fn flatten_nodes(&self, flattened_nodes: &mut Vec<Option<DualNodePtr>>) {
840        let interface = self.read_recursive();
841        let flattened_nodes_length = flattened_nodes.len() as NodeNum;
842        // the order matters: left -> right -> myself
843        if let Some(((left_child_weak, _), (right_child_weak, _))) = &interface.children {
844            left_child_weak.upgrade_force().flatten_nodes(flattened_nodes);
845            right_child_weak.upgrade_force().flatten_nodes(flattened_nodes);
846        }
847        for node_index in 0..interface.nodes_length {
848            let dual_node_ptr = &interface.nodes[node_index];
849            if let Some(dual_node_ptr) = dual_node_ptr {
850                dual_node_ptr.update();
851            }
852            flattened_nodes.push(dual_node_ptr.clone());
853        }
854        debug_assert_eq!(
855            flattened_nodes.len() as NodeNum - flattened_nodes_length,
856            interface.nodes_count()
857        );
858    }
859
860    pub fn create_defect_node(&self, vertex_idx: VertexIndex, dual_module_impl: &mut impl DualModuleImpl) -> DualNodePtr {
861        let belonging = self.downgrade();
862        let mut interface = self.write();
863        interface.sum_grow_speed += 1;
864        let local_node_index = interface.nodes_length;
865        let node_index = interface.nodes_count();
866        // try to reuse existing pointer to avoid list allocation
867        let node_ptr = if !interface.is_fusion
868            && local_node_index < interface.nodes.len()
869            && interface.nodes[local_node_index].is_some()
870        {
871            let node_ptr = interface.nodes[local_node_index].take().unwrap();
872            let mut node = node_ptr.write();
873            node.index = node_index;
874            node.class = DualNodeClass::DefectVertex {
875                defect_index: vertex_idx,
876            };
877            node.grow_state = DualNodeGrowState::Grow;
878            node.parent_blossom = None;
879            node.dual_variable_cache = (0, interface.dual_variable_global_progress);
880            node.belonging = belonging;
881            node.defect_size = nz!(1usize);
882            drop(node);
883            node_ptr
884        } else {
885            DualNodePtr::new_value(DualNode {
886                index: node_index,
887                class: DualNodeClass::DefectVertex {
888                    defect_index: vertex_idx,
889                },
890                grow_state: DualNodeGrowState::Grow,
891                parent_blossom: None,
892                dual_variable_cache: (0, interface.dual_variable_global_progress),
893                belonging,
894                defect_size: nz!(1usize),
895            })
896        };
897        interface.nodes_length += 1;
898        if interface.nodes.len() < interface.nodes_length {
899            interface.nodes.push(None);
900        }
901        let cloned_node_ptr = node_ptr.clone();
902        interface.nodes[local_node_index] = Some(node_ptr); // feature `dangerous_pointer`: must push the owner
903        drop(interface);
904        dual_module_impl.add_defect_node(&cloned_node_ptr);
905        cloned_node_ptr
906    }
907
908    /// check whether a pointer belongs to this node, it will acquire a reader lock on `dual_node_ptr`
909    pub fn check_ptr_belonging(&self, dual_node_ptr: &DualNodePtr) -> bool {
910        let interface = self.read_recursive();
911        let dual_node = dual_node_ptr.read_recursive();
912        if dual_node.index >= interface.nodes_count() {
913            return false;
914        }
915        if let Some(ptr) = interface.get_node(dual_node.index).as_ref() {
916            ptr == dual_node_ptr
917        } else {
918            false
919        }
920    }
921
922    /// create a dual node corresponding to a blossom, automatically set the grow state of internal nodes;
923    /// the nodes circle MUST starts with a growing node and ends with a shrinking node
924    pub fn create_blossom(
925        &self,
926        nodes_circle: Vec<DualNodePtr>,
927        mut touching_children: Vec<(DualNodeWeak, DualNodeWeak)>,
928        dual_module_impl: &mut impl DualModuleImpl,
929    ) -> DualNodePtr {
930        let belonging = self.downgrade();
931        let mut interface = self.write();
932        if touching_children.is_empty() {
933            // automatically fill the children, only works when nodes_circle consists of all syndrome nodes
934            touching_children = nodes_circle.iter().map(|ptr| (ptr.downgrade(), ptr.downgrade())).collect();
935        }
936        debug_assert_eq!(touching_children.len(), nodes_circle.len(), "circle length mismatch");
937        let local_node_index = interface.nodes_length;
938        let node_index = interface.nodes_count();
939        let defect_size = nodes_circle
940            .iter()
941            .map(|iter| iter.read_recursive().defect_size)
942            .reduce(|a, b| a.saturating_add(b.get()))
943            .unwrap();
944
945        let blossom_node_ptr = if !interface.is_fusion
946            && local_node_index < interface.nodes.len()
947            && interface.nodes[local_node_index].is_some()
948        {
949            let node_ptr = interface.nodes[local_node_index].take().unwrap();
950            let mut node = node_ptr.write();
951            node.index = node_index;
952            node.class = DualNodeClass::Blossom {
953                nodes_circle: vec![],
954                touching_children: vec![],
955            };
956            node.grow_state = DualNodeGrowState::Grow;
957            node.parent_blossom = None;
958            node.dual_variable_cache = (0, interface.dual_variable_global_progress);
959            node.belonging = belonging;
960            node.defect_size = defect_size;
961            drop(node);
962            node_ptr
963        } else {
964            DualNodePtr::new_value(DualNode {
965                index: node_index,
966                class: DualNodeClass::Blossom {
967                    nodes_circle: vec![],
968                    touching_children: vec![],
969                },
970                grow_state: DualNodeGrowState::Grow,
971                parent_blossom: None,
972                dual_variable_cache: (0, interface.dual_variable_global_progress),
973                belonging,
974                defect_size,
975            })
976        };
977        drop(interface);
978        for node_ptr in nodes_circle.iter() {
979            debug_assert!(
980                self.check_ptr_belonging(node_ptr),
981                "this ptr doesn't belong to this interface"
982            );
983            let node = node_ptr.read_recursive();
984            debug_assert!(
985                node.parent_blossom.is_none(),
986                "cannot create blossom on a node that already belongs to a blossom"
987            );
988            drop(node);
989            // set state must happen before setting parent
990            self.set_grow_state(node_ptr, DualNodeGrowState::Stay, dual_module_impl);
991            // then update parent
992            let mut node = node_ptr.write();
993            node.parent_blossom = Some(blossom_node_ptr.downgrade());
994        }
995        let mut interface = self.write();
996        if interface.debug_print_actions {
997            eprintln!("[create blossom] {:?} -> {}", nodes_circle, node_index);
998        }
999        let cloned_blossom_node_ptr = blossom_node_ptr.clone();
1000        {
1001            // fill in the nodes because they're in a valid state (all linked to this blossom)
1002            let mut node = blossom_node_ptr.write();
1003            node.class = DualNodeClass::Blossom {
1004                nodes_circle: nodes_circle.iter().map(|ptr| ptr.downgrade()).collect(),
1005                touching_children,
1006            };
1007            interface.nodes_length += 1;
1008            if interface.nodes.len() < interface.nodes_length {
1009                interface.nodes.push(None);
1010            }
1011            drop(node);
1012            interface.nodes[local_node_index] = Some(blossom_node_ptr); // feature `dangerous_pointer`: must push the owner
1013        }
1014        interface.sum_grow_speed += 1;
1015        drop(interface);
1016        dual_module_impl.prepare_nodes_shrink(&nodes_circle);
1017        dual_module_impl.add_blossom(&cloned_blossom_node_ptr);
1018        cloned_blossom_node_ptr
1019    }
1020
1021    /// expand a blossom: note that different from Blossom V library, we do not maintain tree structure after a blossom is expanded;
1022    /// this is because we're growing all trees together, and due to the natural of quantum codes, this operation is not likely to cause
1023    /// bottleneck as long as physical error rate is well below the threshold. All internal nodes will have a [`DualNodeGrowState::Grow`] state afterwards.
1024    pub fn expand_blossom(&self, blossom_node_ptr: DualNodePtr, dual_module_impl: &mut impl DualModuleImpl) {
1025        let interface = self.read_recursive();
1026        if interface.debug_print_actions {
1027            let node = blossom_node_ptr.read_recursive();
1028            if let DualNodeClass::Blossom { nodes_circle, .. } = &node.class {
1029                eprintln!("[expand blossom] {:?} -> {:?}", blossom_node_ptr, nodes_circle);
1030            } else {
1031                unreachable!()
1032            }
1033        }
1034        let is_fusion = interface.is_fusion;
1035        drop(interface);
1036        if is_fusion {
1037            // must update all the nodes before calling `remove_blossom` of the implementation
1038            let node = blossom_node_ptr.read_recursive();
1039            if let DualNodeClass::Blossom { nodes_circle, .. } = &node.class {
1040                for node_weak in nodes_circle.iter() {
1041                    node_weak.upgrade_force().update();
1042                }
1043            }
1044        }
1045        dual_module_impl.remove_blossom(blossom_node_ptr.clone());
1046        let mut interface = self.write();
1047        let node = blossom_node_ptr.read_recursive();
1048        match &node.grow_state {
1049            DualNodeGrowState::Grow => {
1050                interface.sum_grow_speed += -1;
1051            }
1052            DualNodeGrowState::Shrink => {
1053                interface.sum_grow_speed += 1;
1054            }
1055            DualNodeGrowState::Stay => {}
1056        }
1057        let node_idx = node.index;
1058        debug_assert!(
1059            interface.get_node(node_idx).is_some(),
1060            "the blossom should not be expanded before"
1061        );
1062        debug_assert!(
1063            interface.get_node(node_idx).as_ref().unwrap() == &blossom_node_ptr,
1064            "the blossom doesn't belong to this DualModuleInterface"
1065        );
1066        drop(interface);
1067        match &node.class {
1068            DualNodeClass::Blossom { nodes_circle, .. } => {
1069                for node_weak in nodes_circle.iter() {
1070                    let node_ptr = node_weak.upgrade_force();
1071                    let mut node = node_ptr.write();
1072                    debug_assert!(
1073                        node.parent_blossom.is_some()
1074                            && node.parent_blossom.as_ref().unwrap() == &blossom_node_ptr.downgrade(),
1075                        "internal error: parent blossom must be this blossom"
1076                    );
1077                    debug_assert!(
1078                        node.grow_state == DualNodeGrowState::Stay,
1079                        "internal error: children node must be DualNodeGrowState::Stay"
1080                    );
1081                    node.parent_blossom = None;
1082                    drop(node);
1083                    {
1084                        // safest way: to avoid sub-optimal result being found, set all nodes to growing state
1085                        // WARNING: expanding a blossom like this way MAY CAUSE DEADLOCK!
1086                        // think about this extreme case: after a blossom is expanded, they may gradually form a new blossom and needs expanding again!
1087                        self.set_grow_state(&node_ptr, DualNodeGrowState::Grow, dual_module_impl);
1088                        // the solution is to provide two entry points, the two children of this blossom that directly connect to the two + node in the alternating tree
1089                        // only in that way it's guaranteed to make some progress without re-constructing this blossom
1090                        // It's the primal module's responsibility to avoid this happening, using the dual module's API: [``]
1091                    }
1092                }
1093            }
1094            _ => {
1095                unreachable!()
1096            }
1097        }
1098        let mut interface = self.write();
1099        interface.remove_node(node_idx); // remove this blossom from root, feature `dangerous_pointer` requires running this at the end
1100    }
1101
1102    /// a helper function to update grow state
1103    #[allow(clippy::needless_borrow)]
1104    pub fn set_grow_state(
1105        &self,
1106        dual_node_ptr: &DualNodePtr,
1107        grow_state: DualNodeGrowState,
1108        dual_module_impl: &mut impl DualModuleImpl,
1109    ) {
1110        if self.read_recursive().is_fusion {
1111            dual_node_ptr.update(); // these dual node may not be update-to-date in fusion
1112        }
1113        let mut interface = self.write();
1114        if interface.debug_print_actions {
1115            eprintln!("[set grow state] {:?} {:?}", dual_node_ptr, grow_state);
1116        }
1117        {
1118            // update sum_grow_speed and dual variable cache
1119            let mut node = dual_node_ptr.write();
1120            match &node.grow_state {
1121                DualNodeGrowState::Grow => {
1122                    interface.sum_grow_speed -= 1;
1123                }
1124                DualNodeGrowState::Shrink => {
1125                    interface.sum_grow_speed += 1;
1126                }
1127                DualNodeGrowState::Stay => {}
1128            }
1129            match grow_state {
1130                DualNodeGrowState::Grow => {
1131                    interface.sum_grow_speed += 1;
1132                }
1133                DualNodeGrowState::Shrink => {
1134                    interface.sum_grow_speed -= 1;
1135                }
1136                DualNodeGrowState::Stay => {}
1137            }
1138            let current_dual_variable = node.get_dual_variable(&interface);
1139            node.dual_variable_cache = (current_dual_variable, interface.dual_variable_global_progress);
1140            // update the cache
1141        }
1142        drop(interface);
1143        dual_module_impl.set_grow_state(dual_node_ptr, grow_state); // call this before dual node actually sets; to give history information
1144        dual_node_ptr.set_grow_state(grow_state);
1145    }
1146
1147    /// grow the dual module and update [`DualModuleInterface::sum_`]
1148    pub fn grow(&self, length: Weight, dual_module_impl: &mut impl DualModuleImpl) {
1149        dual_module_impl.grow(length);
1150        self.notify_grown(length);
1151    }
1152
1153    /// if a dual module spontaneously grow some value (e.g. with primal offloading), this function should be called
1154    pub fn notify_grown(&self, length: Weight) {
1155        let mut interface = self.write();
1156        interface.sum_dual_variables += length * interface.sum_grow_speed;
1157        interface.dual_variable_global_progress += length;
1158    }
1159
1160    /// grow a specific length globally but iteratively: will try to keep growing that much
1161    pub fn grow_iterative(&self, mut length: Weight, dual_module_impl: &mut impl DualModuleImpl) {
1162        while length > 0 {
1163            let max_update_length = dual_module_impl.compute_maximum_update_length();
1164            let safe_growth = max_update_length
1165                .get_none_zero_growth()
1166                .unwrap_or_else(|| panic!("iterative grow failed because of conflicts {max_update_length:?}"));
1167            let growth = std::cmp::min(length, safe_growth);
1168            self.grow(growth, dual_module_impl);
1169            length -= growth;
1170        }
1171    }
1172
1173    /// fuse two interfaces by copying the nodes in `other` into myself
1174    #[allow(clippy::unnecessary_cast)]
1175    #[allow(clippy::needless_borrow)]
1176    pub fn slow_fuse(&self, left: &Self, right: &Self) {
1177        let mut interface = self.write();
1178        interface.is_fusion = true; // for safety
1179        for other in [left, right] {
1180            let mut other_interface = other.write();
1181            other_interface.is_fusion = true;
1182            let bias = interface.nodes_length as NodeNum;
1183            for other_node_index in 0..other_interface.nodes_length as NodeNum {
1184                let node_ptr = &other_interface.nodes[other_node_index as usize];
1185                if let Some(node_ptr) = node_ptr {
1186                    let mut node = node_ptr.write();
1187                    debug_assert_eq!(node.index, other_node_index);
1188                    node.index += bias;
1189                    node.dual_variable_cache = (
1190                        node.get_dual_variable(&other_interface),
1191                        interface.dual_variable_global_progress,
1192                    )
1193                }
1194                interface.nodes_length += 1;
1195                if interface.nodes.len() < interface.nodes_length {
1196                    interface.nodes.push(None);
1197                }
1198                interface.nodes[(bias + other_node_index) as usize] = node_ptr.clone();
1199            }
1200            interface.sum_dual_variables += other_interface.sum_dual_variables;
1201            interface.sum_grow_speed += other_interface.sum_grow_speed;
1202        }
1203    }
1204
1205    /// fuse two interfaces by (virtually) copying the nodes in `other` into myself, with O(1) time complexity
1206    pub fn fuse(&self, left: &Self, right: &Self) {
1207        let parent_weak = self.downgrade();
1208        let left_weak = left.downgrade();
1209        let right_weak = right.downgrade();
1210        let mut interface = self.write();
1211        interface.is_fusion = true; // for safety
1212        debug_assert_eq!(interface.nodes_length, 0, "fast fuse doesn't support non-empty fuse");
1213        debug_assert!(interface.children.is_none(), "cannot fuse twice");
1214        let mut left_interface = left.write();
1215        let mut right_interface = right.write();
1216        left_interface.is_fusion = true;
1217        right_interface.is_fusion = true;
1218        debug_assert!(left_interface.parent.is_none(), "cannot fuse an interface twice");
1219        debug_assert!(right_interface.parent.is_none(), "cannot fuse an interface twice");
1220        left_interface.parent = Some(parent_weak.clone());
1221        right_interface.parent = Some(parent_weak);
1222        left_interface.index_bias = 0;
1223        right_interface.index_bias = left_interface.nodes_count();
1224        interface.children = Some((
1225            (left_weak, left_interface.nodes_count()),
1226            (right_weak, right_interface.nodes_count()),
1227        ));
1228        for other_interface in [left_interface, right_interface] {
1229            interface.sum_dual_variables += other_interface.sum_dual_variables;
1230            interface.sum_grow_speed += other_interface.sum_grow_speed;
1231        }
1232    }
1233
1234    /// do a sanity check of if all the nodes are in consistent state
1235    #[inline(never)]
1236    #[allow(clippy::unnecessary_cast)]
1237    #[allow(clippy::needless_borrow)]
1238    pub fn sanity_check(&self) -> Result<Vec<Option<DualNodePtr>>, String> {
1239        let mut flattened_nodes = vec![];
1240        self.flatten_nodes(&mut flattened_nodes);
1241        let interface = self.read_recursive();
1242        if false {
1243            eprintln!("[warning] sanity check disabled for dual_module.rs");
1244            return Ok(flattened_nodes);
1245        }
1246        let mut visited_syndrome = HashSet::with_capacity((interface.nodes_count() * 2) as usize);
1247        let mut sum_individual_dual_variable = 0;
1248        for (index, dual_node_ptr) in flattened_nodes.iter().enumerate() {
1249            if let Some(dual_node_ptr) = dual_node_ptr {
1250                let dual_node = dual_node_ptr.read_recursive();
1251                sum_individual_dual_variable += dual_node.get_dual_variable(&interface);
1252                if dual_node.index != index as NodeIndex {
1253                    return Err(format!(
1254                        "dual node index wrong: expected {}, actual {}",
1255                        index, dual_node.index
1256                    ));
1257                }
1258                match &dual_node.class {
1259                    DualNodeClass::Blossom {
1260                        nodes_circle,
1261                        touching_children,
1262                    } => {
1263                        for (idx, circle_node_weak) in nodes_circle.iter().enumerate() {
1264                            let circle_node_ptr = circle_node_weak.upgrade_force();
1265                            if &circle_node_ptr == dual_node_ptr {
1266                                return Err("a blossom should not contain itself".to_string());
1267                            }
1268                            let circle_node = circle_node_ptr.read_recursive();
1269                            if circle_node.parent_blossom.as_ref() != Some(&dual_node_ptr.downgrade()) {
1270                                return Err(format!(
1271                                    "blossom {} contains {} but child's parent pointer = {:?} is not pointing back",
1272                                    dual_node.index, circle_node.index, circle_node.parent_blossom
1273                                ));
1274                            }
1275                            if circle_node.grow_state != DualNodeGrowState::Stay {
1276                                return Err(format!("child node {} is not at Stay state", circle_node.index));
1277                            }
1278                            // check if circle node is still tracked, i.e. inside self.nodes
1279                            if circle_node.index >= interface.nodes_count()
1280                                || interface.get_node(circle_node.index).is_none()
1281                            {
1282                                return Err(format!("child's index {} is not in the interface", circle_node.index));
1283                            }
1284                            let tracked_circle_node_ptr = interface.get_node(circle_node.index).unwrap();
1285                            if tracked_circle_node_ptr != circle_node_ptr {
1286                                return Err(format!(
1287                                    "the tracked ptr of child {} is not what's being pointed",
1288                                    circle_node.index
1289                                ));
1290                            }
1291                            // check children belongings
1292                            let (child_weak_1, child_weak_2) = &touching_children[idx];
1293                            if matches!(circle_node.class, DualNodeClass::DefectVertex { .. }) {
1294                                if child_weak_1 != circle_node_weak {
1295                                    return Err(format!("touching child can only be syndrome node {}", circle_node.index));
1296                                }
1297                                if child_weak_2 != circle_node_weak {
1298                                    return Err(format!("touching child can only be syndrome node {}", circle_node.index));
1299                                }
1300                            } else {
1301                                let child_ptr_1 = child_weak_1.upgrade_force();
1302                                let child_ptr_2 = child_weak_2.upgrade_force();
1303                                let child_1_ancestor = child_ptr_1.get_ancestor_blossom();
1304                                let child_2_ancestor = child_ptr_2.get_ancestor_blossom();
1305                                let circle_ancestor = circle_node_ptr.get_ancestor_blossom();
1306                                if child_1_ancestor != circle_ancestor {
1307                                    return Err(format!("{:?} is not descendent of {}", child_ptr_1, circle_node.index));
1308                                }
1309                                if child_2_ancestor != circle_ancestor {
1310                                    return Err(format!("{:?} is not descendent of {}", child_ptr_2, circle_node.index));
1311                                }
1312                            }
1313                        }
1314                    }
1315                    DualNodeClass::DefectVertex { defect_index } => {
1316                        if visited_syndrome.contains(defect_index) {
1317                            return Err(format!("duplicate defect index: {}", defect_index));
1318                        }
1319                        visited_syndrome.insert(*defect_index);
1320                    }
1321                }
1322                if let Some(parent_blossom_weak) = &dual_node.parent_blossom {
1323                    if dual_node.grow_state != DualNodeGrowState::Stay {
1324                        return Err(format!("child node {} is not at Stay state", dual_node.index));
1325                    }
1326                    let parent_blossom_ptr = parent_blossom_weak.upgrade_force();
1327                    let parent_blossom = parent_blossom_ptr.read_recursive();
1328                    // check if child is actually inside this blossom
1329                    match &parent_blossom.class {
1330                        DualNodeClass::Blossom { nodes_circle, .. } => {
1331                            let mut found_match_count = 0;
1332                            for node_weak in nodes_circle.iter() {
1333                                let node_ptr = node_weak.upgrade_force();
1334                                if &node_ptr == dual_node_ptr {
1335                                    found_match_count += 1;
1336                                }
1337                            }
1338                            if found_match_count != 1 {
1339                                return Err(format!(
1340                                    "{} is the parent of {} but the child only presents {} times",
1341                                    parent_blossom.index, dual_node.index, found_match_count
1342                                ));
1343                            }
1344                        }
1345                        _ => {
1346                            return Err(format!(
1347                                "{}, as the parent of {}, is not a blossom",
1348                                parent_blossom.index, dual_node.index
1349                            ))
1350                        }
1351                    }
1352                    // check if blossom is still tracked, i.e. inside interface.nodes
1353                    if parent_blossom.index >= interface.nodes_count() || interface.get_node(parent_blossom.index).is_none()
1354                    {
1355                        return Err(format!(
1356                            "parent blossom's index {} is not in the interface",
1357                            parent_blossom.index
1358                        ));
1359                    }
1360                    let tracked_parent_blossom_ptr = interface.get_node(parent_blossom.index).unwrap();
1361                    if tracked_parent_blossom_ptr != parent_blossom_ptr {
1362                        return Err(format!(
1363                            "the tracked ptr of parent blossom {} is not what's being pointed",
1364                            parent_blossom.index
1365                        ));
1366                    }
1367                }
1368            }
1369        }
1370        if sum_individual_dual_variable != interface.sum_dual_variables {
1371            return Err(format!(
1372                "internal error: the sum of dual variables is {} but individual sum is {}",
1373                interface.sum_dual_variables, sum_individual_dual_variable
1374            ));
1375        }
1376        Ok(flattened_nodes)
1377    }
1378
1379    pub fn sum_dual_variables(&self) -> Weight {
1380        self.read_recursive().sum_dual_variables
1381    }
1382}
1383
1384impl Ord for MaxUpdateLength {
1385    fn cmp(&self, other: &Self) -> Ordering {
1386        debug_assert!(
1387            !matches!(self, MaxUpdateLength::NonZeroGrow(_)),
1388            "priority ordering is not valid for NonZeroGrow"
1389        );
1390        debug_assert!(
1391            !matches!(other, MaxUpdateLength::NonZeroGrow(_)),
1392            "priority ordering is not valid for NonZeroGrow"
1393        );
1394        if self == other {
1395            return Ordering::Equal;
1396        }
1397        // VertexShrinkStop has the lowest priority: it should be put at the end of any ordered list
1398        // this is because solving VertexShrinkStop conflict is not possible, but when this happens, the primal module
1399        // should have put this node as a "-" node in the alternating tree, so there must be a parent and a child that
1400        // are "+" nodes, conflicting with each other at exactly this VertexShrinkStop node. In this case, as long as
1401        // one solves those "+" nodes conflicting, e.g. forming a blossom, this node's VertexShrinkStop conflict is automatically solved
1402        match (
1403            matches!(self, MaxUpdateLength::VertexShrinkStop(..)),
1404            matches!(other, MaxUpdateLength::VertexShrinkStop(..)),
1405        ) {
1406            (true, false) => return Ordering::Less,    // less priority
1407            (false, true) => return Ordering::Greater, // greater priority
1408            (true, true) => {
1409                return self
1410                    .get_vertex_shrink_stop()
1411                    .unwrap()
1412                    .cmp(&other.get_vertex_shrink_stop().unwrap())
1413            } // don't care, just compare pointer
1414            _ => {}
1415        }
1416        // then, blossom expanding has the low priority, because it's infrequent and expensive
1417        match (
1418            matches!(self, MaxUpdateLength::BlossomNeedExpand(..)),
1419            matches!(other, MaxUpdateLength::BlossomNeedExpand(..)),
1420        ) {
1421            (true, false) => return Ordering::Less,    // less priority
1422            (false, true) => return Ordering::Greater, // greater priority
1423            (true, true) => {
1424                return self
1425                    .get_blossom_need_expand()
1426                    .unwrap()
1427                    .cmp(&other.get_blossom_need_expand().unwrap())
1428            } // don't care, just compare pointer
1429            _ => {}
1430        }
1431        // We'll prefer match nodes internally instead of to boundary, because there might be less path connecting to boundary
1432        // this is only an attempt to optimize the MWPM decoder, but anyway it won't be an optimal decoder
1433        match (
1434            matches!(self, MaxUpdateLength::TouchingVirtual(..)),
1435            matches!(other, MaxUpdateLength::TouchingVirtual(..)),
1436        ) {
1437            (true, false) => return Ordering::Less,    // less priority
1438            (false, true) => return Ordering::Greater, // greater priority
1439            (true, true) => {
1440                let (a, c) = self.get_touching_virtual().unwrap();
1441                let (b, d) = other.get_touching_virtual().unwrap();
1442                return a.cmp(&b).reverse().then(c.cmp(&d).reverse());
1443            } // don't care, just compare pointer
1444            _ => {}
1445        }
1446        // last, both of them MUST be MaxUpdateLength::Conflicting
1447        let (a, c) = self.get_conflicting().unwrap();
1448        let (b, d) = other.get_conflicting().unwrap();
1449        a.cmp(&b).reverse().then(c.cmp(&d).reverse())
1450    }
1451}
1452
1453impl PartialOrd for MaxUpdateLength {
1454    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1455        Some(self.cmp(other))
1456    }
1457}
1458
1459impl MaxUpdateLength {
1460    /// update all the interface nodes to be up-to-date
1461    pub fn update(&self) {
1462        match self {
1463            Self::NonZeroGrow(_) => {}
1464            Self::Conflicting((a, b), (c, d)) => {
1465                for x in [a, b, c, d] {
1466                    x.update();
1467                }
1468            }
1469            Self::TouchingVirtual((a, b), _) => {
1470                for x in [a, b] {
1471                    x.update();
1472                }
1473            }
1474            Self::BlossomNeedExpand(x) => {
1475                x.update();
1476            }
1477            Self::VertexShrinkStop((a, option)) => {
1478                a.update();
1479                if let Some((b, c)) = option {
1480                    b.update();
1481                    c.update();
1482                }
1483            }
1484        }
1485    }
1486
1487    /// useful function to assert expected case
1488    #[allow(dead_code)]
1489    pub fn is_conflicting(&self, a: &DualNodePtr, b: &DualNodePtr) -> bool {
1490        if let MaxUpdateLength::Conflicting((n1, _), (n2, _)) = self {
1491            if n1 == a && n2 == b {
1492                return true;
1493            }
1494            if n1 == b && n2 == a {
1495                return true;
1496            }
1497        }
1498        false
1499    }
1500
1501    /// helper function that get values out of the enum
1502    #[allow(dead_code)]
1503    #[inline(always)]
1504    pub fn get_none_zero_growth(&self) -> Option<Weight> {
1505        match self {
1506            Self::NonZeroGrow((length, _has_empty_boundary_node)) => Some(*length),
1507            _ => None,
1508        }
1509    }
1510
1511    /// helper function that get values out of the enum
1512    #[allow(dead_code)]
1513    #[inline(always)]
1514    pub fn get_conflicting(&self) -> Option<(DualNodePtr, DualNodePtr)> {
1515        match self {
1516            Self::Conflicting((a, _), (b, _)) => Some((a.clone(), b.clone())),
1517            _ => None,
1518        }
1519    }
1520
1521    /// helper function that get values out of the enum
1522    #[allow(dead_code)]
1523    #[inline(always)]
1524    pub fn get_touching_virtual(&self) -> Option<(DualNodePtr, VertexIndex)> {
1525        match self {
1526            Self::TouchingVirtual((a, _), (b, _)) => Some((a.clone(), *b)),
1527            _ => None,
1528        }
1529    }
1530
1531    /// helper function that get values out of the enum
1532    #[allow(dead_code)]
1533    #[inline(always)]
1534    pub fn get_blossom_need_expand(&self) -> Option<DualNodePtr> {
1535        match self {
1536            Self::BlossomNeedExpand(a) => Some(a.clone()),
1537            _ => None,
1538        }
1539    }
1540
1541    /// helper function that get values out of the enum
1542    #[allow(dead_code)]
1543    #[inline(always)]
1544    pub fn get_vertex_shrink_stop(&self) -> Option<DualNodePtr> {
1545        match self {
1546            Self::VertexShrinkStop((a, _)) => Some(a.clone()),
1547            _ => None,
1548        }
1549    }
1550}
1551
1552/// temporarily remember the weights that has been changed, so that it can revert back
1553#[derive(Debug, Clone)]
1554pub struct EdgeWeightModifier {
1555    /// edge with changed weighted caused by the erasure or X/Z correlation
1556    pub modified: Vec<(EdgeIndex, Weight)>,
1557}
1558
1559impl Default for EdgeWeightModifier {
1560    fn default() -> Self {
1561        Self::new()
1562    }
1563}
1564
1565impl EdgeWeightModifier {
1566    pub fn new() -> Self {
1567        Self { modified: vec![] }
1568    }
1569
1570    /// record the modified edge
1571    pub fn push_modified_edge(&mut self, erasure_edge: EdgeIndex, original_weight: Weight) {
1572        self.modified.push((erasure_edge, original_weight));
1573    }
1574
1575    /// if some edges are not recovered
1576    pub fn has_modified_edges(&self) -> bool {
1577        !self.modified.is_empty()
1578    }
1579
1580    /// retrieve the last modified edge, panic if no more modified edges
1581    pub fn pop_modified_edge(&mut self) -> (EdgeIndex, Weight) {
1582        self.modified
1583            .pop()
1584            .expect("no more modified edges, please check `has_modified_edges` before calling this method")
1585    }
1586}
1587
1588impl std::ops::Deref for EdgeWeightModifier {
1589    type Target = Vec<(EdgeIndex, Weight)>;
1590
1591    fn deref(&self) -> &Self::Target {
1592        &self.modified
1593    }
1594}