Skip to content

Commit ee2d08e

Browse files
committed
spiderize_nodes: spiderize a subset of nodes
1 parent ebe7fa9 commit ee2d08e

2 files changed

Lines changed: 124 additions & 15 deletions

File tree

src/lax/spider.rs

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,45 @@ impl<O: Clone + PartialEq, A: Clone> OpenHypergraph<O, A> {
3737
/// means that the quotient attempted to identify nodes with different
3838
/// labels; the returned finite function is the quotient witness, as for
3939
/// [`OpenHypergraph::quotient`].
40-
pub fn spiderize(mut self) -> Result<OpenHypergraph<O, WithSpider<A>>, FiniteFunction> {
41-
self.quotient()?;
40+
pub fn spiderize(self) -> Result<OpenHypergraph<O, WithSpider<A>>, FiniteFunction> {
41+
let nodes: Vec<NodeId> = (0..self.hypergraph.nodes.len()).map(NodeId).collect();
42+
self.spiderize_nodes(&nodes)
43+
}
44+
45+
/// Replace the chosen nodes' implicit wiring with explicit spider
46+
/// operations.
47+
///
48+
/// Each selected node is replaced by the same pair of spiders used by
49+
/// [`Self::spiderize`]. Unselected nodes retain their implicit wiring.
50+
/// Consequently, unlike [`Self::spiderize`], this operation does not by
51+
/// itself guarantee that the result is acyclic or monogamous.
52+
///
53+
/// `nodes` contains IDs from the input hypergraph. Pending identifications
54+
/// are applied first, and IDs in `nodes` are mapped through the resulting
55+
/// quotient. Selecting any representative therefore selects its complete
56+
/// equivalence class. Duplicate selections are ignored.
57+
///
58+
/// # Panics
59+
///
60+
/// Panics if a selected node ID is out of bounds.
61+
pub fn spiderize_nodes(
62+
mut self,
63+
nodes: &[NodeId],
64+
) -> Result<OpenHypergraph<O, WithSpider<A>>, FiniteFunction> {
65+
let input_node_count = self.hypergraph.nodes.len();
66+
for node in nodes {
67+
assert!(
68+
node.0 < input_node_count,
69+
"node id {:?} is out of bounds",
70+
node
71+
);
72+
}
73+
74+
let quotient = self.quotient()?;
75+
let mut selected = vec![false; quotient.target];
76+
for node in nodes {
77+
selected[quotient.table[node.0]] = true;
78+
}
4279

4380
let OpenHypergraph {
4481
sources,
@@ -75,19 +112,27 @@ impl<O: Clone + PartialEq, A: Clone> OpenHypergraph<O, A> {
75112
.sources
76113
.into_iter()
77114
.map(|node| {
78-
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
79-
left_targets[node.0].push(occurrence);
80-
occurrence
115+
if selected[node.0] {
116+
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
117+
left_targets[node.0].push(occurrence);
118+
occurrence
119+
} else {
120+
central[node.0]
121+
}
81122
})
82123
.collect();
83124

84125
let operation_targets = adjacency
85126
.targets
86127
.into_iter()
87128
.map(|node| {
88-
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
89-
right_sources[node.0].push(occurrence);
90-
occurrence
129+
if selected[node.0] {
130+
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
131+
right_sources[node.0].push(occurrence);
132+
occurrence
133+
} else {
134+
central[node.0]
135+
}
91136
})
92137
.collect();
93138

@@ -103,27 +148,40 @@ impl<O: Clone + PartialEq, A: Clone> OpenHypergraph<O, A> {
103148
result.sources = sources
104149
.into_iter()
105150
.map(|node| {
106-
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
107-
left_sources[node.0].push(occurrence);
108-
occurrence
151+
if selected[node.0] {
152+
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
153+
left_sources[node.0].push(occurrence);
154+
occurrence
155+
} else {
156+
central[node.0]
157+
}
109158
})
110159
.collect();
111160

112161
result.targets = targets
113162
.into_iter()
114163
.map(|node| {
115-
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
116-
right_targets[node.0].push(occurrence);
117-
occurrence
164+
if selected[node.0] {
165+
let occurrence = result.new_node(hypergraph.nodes[node.0].clone());
166+
right_targets[node.0].push(occurrence);
167+
occurrence
168+
} else {
169+
central[node.0]
170+
}
118171
})
119172
.collect();
120173

121-
for (((left_sources, left_targets), right_sources), right_targets) in left_sources
174+
for (node, (((left_sources, left_targets), right_sources), right_targets)) in left_sources
122175
.into_iter()
123176
.zip(left_targets)
124177
.zip(right_sources)
125178
.zip(right_targets)
179+
.enumerate()
126180
{
181+
if !selected[node] {
182+
continue;
183+
}
184+
127185
result.new_edge(
128186
WithSpider::Spider {
129187
sources: left_sources.len(),

tests/lax/spider.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,42 @@ fn spiderize_rejects_inconsistent_quotients() {
139139
assert!(f.spiderize().is_err());
140140
}
141141

142+
#[test]
143+
fn spiderize_nodes_only_replaces_selected_nodes() {
144+
let mut f = OpenHypergraph::empty();
145+
let x = f.new_node(());
146+
let y = f.new_node(());
147+
f.new_edge("forward", ([x], [y]));
148+
f.new_edge("backward", ([y], [x]));
149+
150+
let original = f.clone();
151+
let spiderized = f.spiderize_nodes(&[x]).unwrap();
152+
153+
assert_eq!(
154+
spiderized
155+
.hypergraph
156+
.edges
157+
.iter()
158+
.filter(|edge| matches!(edge, WithSpider::Spider { .. }))
159+
.count(),
160+
2
161+
);
162+
assert!(spiderized.clone().to_strict().is_acyclic());
163+
assert_eq!(forget_spiders(spiderized), original);
164+
}
165+
166+
#[test]
167+
fn spiderize_nodes_maps_selections_through_quotient() {
168+
let mut f = OpenHypergraph::<(), ()>::empty();
169+
let x = f.new_node(());
170+
let y = f.new_node(());
171+
f.unify(x, y);
172+
173+
let spiderized = f.spiderize_nodes(&[y, y]).unwrap();
174+
175+
assert_eq!(spiderized.hypergraph.edges.len(), 2);
176+
}
177+
142178
proptest! {
143179
#[test]
144180
fn spiderize_is_acyclic_monogamous_and_forgetful(
@@ -159,4 +195,19 @@ proptest! {
159195
);
160196
assert_eq!(forget_spiders(spiderized), input);
161197
}
198+
199+
#[test]
200+
fn spiderize_delegates_to_spiderize_nodes(
201+
strict_input in arb_open_hypergraph()
202+
) {
203+
let input: OpenHypergraph<Obj, Arr> =
204+
OpenHypergraph::from_strict(strict_input);
205+
let nodes: Vec<NodeId> =
206+
(0..input.hypergraph.nodes.len()).map(NodeId).collect();
207+
208+
assert_eq!(
209+
input.clone().spiderize().unwrap(),
210+
input.spiderize_nodes(&nodes).unwrap()
211+
);
212+
}
162213
}

0 commit comments

Comments
 (0)