Skip to main content

open_hypergraphs/lax/
spider.rs

1//! Make the Frobenius structure of an open hypergraph explicit.
2
3use super::{Hyperedge, NodeId, OpenHypergraph};
4use crate::strict::vec::FiniteFunction;
5
6/// An operation from `A`, or an explicitly represented spider.
7///
8/// A spider's arity is determined by its corresponding [`Hyperedge`].
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub enum WithSpider<A> {
12    Operation(A),
13    Spider,
14}
15
16impl<O: Clone + PartialEq, A: Clone> OpenHypergraph<O, A> {
17    /// Replace implicit wiring with explicit spider operations.
18    ///
19    /// The result is strict (has no pending quotient), monogamous, and
20    /// acyclic. Each original operation is retained as
21    /// [`WithSpider::Operation`].
22    ///
23    /// For every original node, the construction inserts two spiders:
24    ///
25    /// - `p -> 1 + m` on the left, where `p` is the number of global source
26    ///   occurrences and `m` is the number of operation-source occurrences;
27    /// - `1 + n -> q` on the right, where `n` is the number of
28    ///   operation-target occurrences and `q` is the number of global target
29    ///   occurrences.
30    ///
31    /// The extra leg connects the two spiders. Every other occurrence gets a
32    /// distinct node, making every node occur exactly once as a source and
33    /// exactly once as a target (counting the global interfaces).
34    ///
35    /// Pending identifications in the lax quotient are applied first. An error
36    /// means that the quotient attempted to identify nodes with different
37    /// labels; the returned finite function is the quotient witness, as for
38    /// [`OpenHypergraph::quotient`].
39    pub fn spiderize(self) -> Result<OpenHypergraph<O, WithSpider<A>>, FiniteFunction> {
40        let nodes: Vec<NodeId> = (0..self.hypergraph.nodes.len()).map(NodeId).collect();
41        self.spiderize_nodes(&nodes)
42    }
43
44    /// Replace the chosen nodes' implicit wiring with explicit spider
45    /// operations.
46    ///
47    /// Each selected node is replaced by the same pair of spiders used by
48    /// [`Self::spiderize`]. Unselected nodes retain their implicit wiring.
49    /// Consequently, unlike [`Self::spiderize`], this operation does not by
50    /// itself guarantee that the result is acyclic or monogamous.
51    ///
52    /// `nodes` contains IDs from the input hypergraph. Pending identifications
53    /// are applied first, and IDs in `nodes` are mapped through the resulting
54    /// quotient. Selecting any representative therefore selects its complete
55    /// equivalence class. Duplicate selections are ignored.
56    ///
57    /// # Panics
58    ///
59    /// Panics if a selected node ID is out of bounds.
60    pub fn spiderize_nodes(
61        mut self,
62        nodes: &[NodeId],
63    ) -> Result<OpenHypergraph<O, WithSpider<A>>, FiniteFunction> {
64        let input_node_count = self.hypergraph.nodes.len();
65        for node in nodes {
66            assert!(
67                node.0 < input_node_count,
68                "node id {:?} is out of bounds",
69                node
70            );
71        }
72
73        // Quotient the input if necessary and remap the selected node IDs.
74        let remapped_nodes;
75        let nodes = if self.hypergraph.is_strict() {
76            nodes
77        } else {
78            let quotient = self.quotient()?;
79            remapped_nodes = nodes
80                .iter()
81                .map(|node| NodeId(quotient.table[node.0]))
82                .collect::<Vec<_>>();
83            remapped_nodes.as_slice()
84        };
85
86        assert_eq!(
87            self.hypergraph.edges.len(),
88            self.hypergraph.adjacency.len(),
89            "malformed hypergraph: edges and adjacency lengths differ"
90        );
91
92        // Split selected-node occurrences and record the spiders that reconnect them.
93        let spiders = rewrite_occurrences(
94            nodes,
95            &mut self.hypergraph.nodes,
96            &mut self.hypergraph.adjacency,
97            &mut self.sources,
98            &mut self.targets,
99        );
100
101        // Reuse the input graph, wrapping its existing operation labels.
102        let mut result = self.map_edges(WithSpider::Operation);
103
104        // Append the two explicit operations for each selected node.
105        for (left_spider, right_spider) in spiders {
106            result.new_edge(WithSpider::Spider, left_spider);
107            result.new_edge(WithSpider::Spider, right_spider);
108        }
109
110        Ok(result)
111    }
112}
113
114/// Append a fresh occurrence of `node`, carrying the same label.
115fn new_occurrence<O: Clone>(nodes: &mut Vec<O>, node: NodeId) -> NodeId {
116    let occurrence = NodeId(nodes.len());
117    nodes.push(nodes[node.0].clone());
118    occurrence
119}
120
121/// Replace selected node occurrences and build their spider interfaces.
122///
123/// For every selected node, the returned vector contains a pair consisting of:
124///
125/// - a left spider whose sources are global-source occurrences and whose
126///   targets are the original node followed by operation-source occurrences;
127/// - a right spider whose sources are the original node followed by
128///   operation-target occurrences and whose targets are global-target
129///   occurrences.
130///
131/// Each selected occurrence is replaced in-place with a fresh node carrying
132/// the original label. Unselected occurrences remain unchanged.
133fn rewrite_occurrences<O: Clone>(
134    selected: &[NodeId],
135    nodes: &mut Vec<O>,
136    adjacency: &mut [Hyperedge],
137    sources: &mut [NodeId],
138    targets: &mut [NodeId],
139) -> Vec<(Hyperedge, Hyperedge)> {
140    let node_count = nodes.len();
141
142    // Index spiders by original node while rewriting, but only create selected pairs.
143    let mut spiders: Vec<Option<(Hyperedge, Hyperedge)>> = (0..node_count).map(|_| None).collect();
144    for &node in selected {
145        spiders[node.0].get_or_insert_with(|| {
146            (
147                Hyperedge {
148                    sources: vec![],
149                    targets: vec![node],
150                },
151                Hyperedge {
152                    sources: vec![node],
153                    targets: vec![],
154                },
155            )
156        });
157    }
158
159    // Operation sources leave the left spider; operation targets enter the
160    // right spider.
161    for adjacency in adjacency {
162        for node in &mut adjacency.sources {
163            let original = *node;
164            if let Some((left_spider, _)) = spiders[original.0].as_mut() {
165                let occurrence = new_occurrence(nodes, original);
166                left_spider.targets.push(occurrence);
167                *node = occurrence;
168            }
169        }
170
171        for node in &mut adjacency.targets {
172            let original = *node;
173            if let Some((_, right_spider)) = spiders[original.0].as_mut() {
174                let occurrence = new_occurrence(nodes, original);
175                right_spider.sources.push(occurrence);
176                *node = occurrence;
177            }
178        }
179    }
180
181    // Global sources enter the left spider; global targets leave the right.
182    for node in sources {
183        let original = *node;
184        if let Some((left_spider, _)) = spiders[original.0].as_mut() {
185            let occurrence = new_occurrence(nodes, original);
186            left_spider.sources.push(occurrence);
187            *node = occurrence;
188        }
189    }
190
191    for node in targets {
192        let original = *node;
193        if let Some((_, right_spider)) = spiders[original.0].as_mut() {
194            let occurrence = new_occurrence(nodes, original);
195            right_spider.targets.push(occurrence);
196            *node = occurrence;
197        }
198    }
199
200    spiders.into_iter().flatten().collect()
201}