1use petgraph::Graph;
6use yui_core::num::Sign;
7use yui_core::ext::CloneAnd;
8use yui_core::bitseq::Bit;
9
10use super::{Edge, Link, LinkBuilder, NodeType, Path, Slot, State};
11
12impl Link {
13 pub fn conn_sum(&self, other: &Link) -> Link {
15 let self_e = self.base_pt().expect("self needs a base point");
16 let other_e = other.base_pt().expect("other needs a base point");
17 self.conn_sum_at(other, self_e, other_e)
18 }
19
20 pub fn conn_sum_at(&self, other: &Link, self_e: Edge, other_e: Edge) -> Link {
23 assert!(self.is_oriented(), "conn_sum requires an oriented link (self)");
24 assert!(other.is_oriented(), "conn_sum requires an oriented link (other)");
25 assert!(
26 !self.loops().contains(&self_e) && !other.loops().contains(&other_e),
27 "connected sum on free loops is not supported yet"
28 );
29
30 let mut b = LinkBuilder::new();
31 let v1 = b.add_link(self);
32 let v2 = b.add_link(other);
33
34 let port = |verts: &[_], (i, s): (usize, Slot)| (verts[i], s.index());
36 let (t1, h1) = self.edge_ends(self_e, true);
37 let (t2, h2) = other.edge_ends(other_e, true);
38 let (t1, h1) = (port(&v1, t1), port(&v1, h1));
39 let (t2, h2) = (port(&v2, t2), port(&v2, h2));
40
41 b.disconnect(h1); b.disconnect(h2); b.connect(t1, h2); b.connect(t2, h1); let base = self.base_pt().map(|e|
53 if e != self_e {
54 b.edge_at(port(&v1, self.edge_ends(e, false).0)).unwrap()
55 } else {
56 b.edge_at(h1).unwrap()
57 }
58 );
59
60 let n1 = self.n_nodes();
63 let sum = b.build_with(|i, s| {
64 let (l, i) = if i < n1 { (self, i) } else { (other, i - n1) };
65 l.node(i).is_incoming(Slot::from(s))
66 }).unwrap();
67
68 match base {
69 Some(e) => sum.with_base_pt(e),
70 None => sum,
71 }
72 }
73
74 pub fn mirror(&self) -> Self {
75 let l = Self::new(
76 self.nodes().map(|x| x.mirror()),
77 self.loops().iter().copied(),
78 );
79 match self.base_pt() {
82 Some(e) => l.with_base_pt(e),
83 None => l,
84 }
85 }
86
87 pub fn reversed(&self) -> Self {
90 let l = Self::new(
91 self.nodes().map(|x| x.reversed()),
92 self.loops().iter().copied(),
93 );
94 match self.base_pt() {
97 Some(e) => l.with_base_pt(e),
98 None => l,
99 }
100 }
101
102 pub fn cc_at(&self, i: usize) -> Self {
103 assert!(self.node(i).is_crossing());
104 self.clone_and(|l|
105 *l.node_mut(i) = l.node(i).mirror()
106 )
107 }
108
109 pub fn resolve_at(&self, i: usize, r: Bit) -> Self {
110 assert!(self.node(i).is_crossing());
111 self.clone_and(|l| {
112 *l.node_mut(i) = l.node(i).resolve(r);
113 l.normalize_ori();
114 })
115 }
116
117 pub fn resolve_by(&self, s: &State) -> Self {
118 assert!(s.len() == self.n_crossings());
119
120 let n = self.n_nodes();
121 let itr = (0..n).filter(|&i| self.node(i).is_crossing());
122
123 self.clone_and(|l| {
124 for (i, r) in Iterator::zip(itr, s.iter()) {
125 *l.node_mut(i) = self.node(i).resolve(r);
126 }
127 l.normalize_ori();
128 })
129 }
130
131 pub fn seifert_state(&self) -> State {
132 assert!(self.is_oriented());
133
134 let seq = self.crossings().map(|x|
135 match x.sign() {
136 Some(Sign::Pos) => 0,
137 Some(Sign::Neg) => 1,
138 None => panic!("Impossible.")
139 }
140 );
141 State::from_iter(seq)
142 }
143
144 pub fn seifert_circles(&self) -> Vec<Path> {
145 self.resolve_by(&self.seifert_state()).comps()
146 }
147
148 pub fn seifert_graph(&self) -> Graph<Path, usize> {
149 assert!(self.is_oriented());
150
151 type G = Graph<Path, usize>;
152
153 let s0 = self.seifert_state();
154 let l0 = self.resolve_by(&s0);
155 let mut graph = Graph::new();
156
157 for c in l0.comps() {
159 graph.add_node(c);
160 }
161
162 let find_node = |graph: &G, e| {
163 graph.node_indices().find(|&i|
164 graph[i].contains(e)
165 )
166 };
167
168 for (i, x) in l0.nodes().enumerate() {
171 let (e1, e2) = if x.node_type() == NodeType::V {
172 (x.edge(Slot::SW), x.edge(Slot::SE))
173 } else {
174 (x.edge(Slot::SW), x.edge(Slot::NE))
175 };
176 let n1 = find_node(&graph, e1).unwrap();
177 let n2 = find_node(&graph, e2).unwrap();
178 graph.add_edge(n1, n2, i);
179 }
180
181 graph
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::{Braid, Node};
189 use crate::NodeType::{XL, XR};
190 use crate::misc::jones_polynomial;
191
192 #[test]
193 fn link_reversed() {
194 let l = Link::test_data("3_1");
195 let r = l.reversed();
196
197 assert!(r.is_oriented());
198 assert_eq!(r.writhe(), l.writhe(), "reversing a knot keeps every crossing sign");
199 assert_eq!(r.base_pt(), l.base_pt());
200
201 for (x, y) in Iterator::zip(l.nodes(), r.nodes()) {
203 assert_eq!(y.node_type(), x.node_type());
204 assert_eq!(y.edges(), x.edges());
205 }
206 assert_eq!(r.reversed(), l, "reversing twice is the identity");
207
208 for (x, y) in Iterator::zip(l.nodes(), r.nodes()) {
210 let (p, q) = x.incoming().unwrap();
211 assert_eq!(y.incoming(), Some((x.paired_slot(p).min(x.paired_slot(q)),
212 x.paired_slot(p).max(x.paired_slot(q)))));
213 }
214 }
215
216 #[test]
217 fn link_mirror() {
218 let l = Link::test_data("unknot_l_twist");
219 assert_eq!(l.node(0).node_type(), XL);
220
221 let l = l.mirror();
222 assert_eq!(l.node(0).node_type(), XR);
223 }
224
225 #[test]
226 fn mirror_preserves_loops() {
227 let l = Link::unlink(1).mirror();
228 assert_eq!(l.n_loops(), 1);
229 assert_eq!(l.loops(), &[1]);
230 }
231
232 #[test]
233 fn mirror_preserves_base_pt() {
234 let l = Link::test_data("3_1").with_base_pt(3).mirror();
235 assert_eq!(l.base_pt(), Some(3));
236 }
237
238 #[test]
239 fn crossing_change() {
240 let l = Link::test_data("3_1");
241 let l2 = l.cc_at(1);
242
243 assert_eq!(l.node(1), &Node::new(XL, Some((Slot::SW, Slot::NW)), [3,1,4,6]));
244 assert_eq!(l2.node(1), &Node::new(XR, Some((Slot::SW, Slot::NW)), [3,1,4,6]));
245 }
246
247 #[test]
248 fn resolve_drops_the_orientation_wholesale() {
249 let l = Link::test_data("3_1");
252 assert_eq!(l.seifert_state(), State::from([0, 0, 0]));
253
254 assert!(l.resolve_by(&State::from([0, 0, 0])).is_oriented());
255 for st in [[0, 0, 1], [0, 1, 1], [1, 1, 1]] {
256 let r = l.resolve_by(&State::from(st));
257 assert!(!r.is_oriented(), "{st:?} left a partial orientation");
258 r.verify_ori();
259 }
260 }
261
262 #[test]
263 fn link_resolve() {
264 let s = State::from([0, 0, 0]);
265 let l = Link::test_data("3_1").resolve_by(&s);
266
267 let comps = l.comps();
268 assert_eq!(comps.len(), 2);
269 assert!(comps.iter().all(|c| c.is_circle()));
270
271 let s = State::from([1, 1, 1]);
272 let l = Link::test_data("3_1").resolve_by(&s);
273
274 let comps = l.comps();
275 assert_eq!(comps.len(), 3);
276 assert!(comps.iter().all(|c| c.is_circle()));
277 }
278
279 #[test]
280 fn seifert_graph_trefoil() {
281 let l = Link::test_data("3_1");
282 let g = l.seifert_graph();
283 assert_eq!(g.node_count(), 2);
284 assert_eq!(g.edge_count(), 3);
285 }
286
287 #[test]
288 fn seifert_graph_unlink() {
289 let l = Link::unlink(3);
291 let g = l.seifert_graph();
292 assert_eq!(g.node_count(), 3);
293 assert_eq!(g.edge_count(), 0);
294 }
295
296 #[test]
297 fn conn_sum_is_jones_multiplicative() {
298 let k1 = Link::test_data("3_1");
300 let k2 = Link::test_data("4_1");
301 let cs = k1.conn_sum(&k2); assert_eq!(cs.n_comps(), 1);
303 assert_eq!(cs.n_crossings(), k1.n_crossings() + k2.n_crossings());
304 assert!(cs.is_oriented());
305
306 let (vcs, vu) = (jones_polynomial(&cs), jones_polynomial(&Link::unknot()));
307 let (v1, v2) = (jones_polynomial(&k1), jones_polynomial(&k2));
308 assert_eq!(&vcs * &vu, &v1 * &v2);
309
310 let cs2 = k1.conn_sum_at(&k2, 4, 6);
312 assert_eq!(jones_polynomial(&cs2), jones_polynomial(&cs));
313 }
314
315 #[test]
316 fn conn_sum_of_braid_closures() {
317 let k1 = Braid::from([1, 1, 1]).closure(); let k2 = Braid::from([1, -2, 1, -2]).closure(); let cs = k1.conn_sum(&k2);
321 assert_eq!(cs.n_comps(), 1);
322 assert!(cs.is_oriented());
323
324 let (vcs, vu) = (jones_polynomial(&cs), jones_polynomial(&Link::unknot()));
325 let (v1, v2) = (jones_polynomial(&k1), jones_polynomial(&k2));
326 assert_eq!(&vcs * &vu, &v1 * &v2);
327 }
328
329 #[test]
333 fn conn_sum_of_two_curls() {
334 let k = Link::test_data("unknot_l_twist");
336 let cs = k.conn_sum(&k);
337 assert_eq!(cs.pd_code(), [[1,3,2,2],[3,1,4,4]]);
338 assert_eq!(cs.base_pt(), Some(1));
339 }
340
341 #[test]
342 fn conn_sum_of_a_curl_and_its_reverse() {
343 let k = Link::test_data("unknot_l_twist");
345 let cs = k.conn_sum(&k.reversed());
346 assert_eq!(cs.pd_code(), [[1,3,2,2],[4,4,1,3]]);
347 assert_eq!(cs.base_pt(), Some(1));
348 }
349
350 #[test]
351 fn conn_sum_of_a_curl_and_its_mirror() {
352 let k = Link::test_data("unknot_l_twist");
354 let cs = k.conn_sum(&k.mirror());
355 assert_eq!(cs.pd_code(), [[1,3,2,2],[4,3,1,4]]);
356 assert_eq!(cs.base_pt(), Some(1));
357 }
358
359 #[test]
360 fn conn_sum_of_a_curl_and_its_concordance_inverse() {
361 let k = Link::test_data("unknot_l_twist");
363 let cs = k.conn_sum(&k.mirror().reversed());
364 assert_eq!(cs.pd_code(), [[1,3,2,2],[3,4,4,1]]);
365 assert_eq!(cs.base_pt(), Some(1));
366 }
367
368 #[test]
369 fn conn_sum_at_of_two_curls_away_from_the_base_pt() {
370 let k = Link::test_data("unknot_l_twist");
373 let cs = k.conn_sum_at(&k, 2, 2);
374 assert_eq!(cs.pd_code(), [[1,1,4,2],[3,3,2,4]]);
375 assert_eq!(cs.base_pt(), Some(1));
376
377 assert_eq!(cs.reindexed(1, 1).pd_code(), Link::test_data("unknot_l_twist2").pd_code());
380 }
381
382 #[test]
383 fn conn_sum_at_of_two_curls_on_different_edges() {
384 let k = Link::test_data("unknot_l_twist");
387 let cs = k.conn_sum_at(&k, 2, 1);
388 assert_eq!(cs.pd_code(), [[1,1,3,2],[3,2,4,4]]);
389 assert_eq!(cs.base_pt(), Some(1));
390
391 let expected = Link::from_pd_code([[1,1,2,3],[2,3,4,4]]);
393 assert_eq!(cs.reindexed(1, 1).pd_code(), expected.reindexed(1, 1).pd_code());
394 }
395
396 #[test]
397 fn dms_construction() {
398 let k = Link::from_pd_code([[1,4,2,5],[5,2,6,3],[3,6,4,1]]);
402 let mk = k.mirror();
403
404 let k2 = k.conn_sum(&k);
407 assert_eq!(k2.pd_code(), Link::from_pd_code([
408 [1,4,2,5],[5,2,6,3],[3,6,4,7],[7,10,8,11],[11,8,12,9],[9,12,10,1],
409 ]).pd_code());
410 assert_eq!(k2.base_pt(), Some(1));
411
412 let with_k = k2.conn_sum_at(&k, 7, 1);
415 assert_eq!(with_k.pd_code(), Link::from_pd_code([
416 [1,4,2,5],[5,2,6,3],[3,6,4,13],[7,10,8,11],[11,8,12,9],[9,12,10,1],
417 [13,16,14,17],[17,14,18,15],[15,18,16,7],
418 ]).pd_code());
419 assert_eq!(with_k.base_pt(), Some(1));
420
421 let with_mk = k2.conn_sum_at(&mk, 7, 1);
424 assert_eq!(with_mk.pd_code(), Link::from_pd_code([
425 [1,4,2,5],[5,2,6,3],[3,6,4,13],[7,10,8,11],[11,8,12,9],[9,12,10,1],
426 [16,14,17,13],[14,18,15,17],[18,16,7,15],
427 ]).pd_code());
428 assert_eq!(with_mk.base_pt(), Some(1));
429
430 let dms = with_mk.conn_sum_at(&mk.reversed(), 16, 1);
432 assert_eq!(dms.pd_code(), Link::from_pd_code([
433 [1,4,2,5],[5,2,6,3],[3,6,4,13],[7,10,8,11],[11,8,12,9],[9,12,10,1],
434 [16,14,17,13],[14,18,15,17],[18,19,7,15],
435 [19,21,24,22],[21,23,20,24],[23,16,22,20],
436 ]).pd_code());
437 assert_eq!(dms.base_pt(), Some(1));
438 }
439
440 #[test]
441 fn conn_sum_keeps_each_summand_orientation() {
442 for (a, b) in [("3_1", "4_1"), ("4_1", "3_1"), ("unknot_l_twist", "3_1"), ("6_2", "5_1")] {
445 for (rev1, rev2) in [(false, false), (true, false), (false, true), (true, true)] {
446 let k1 = Link::test_data(a).clone_and(|l| if rev1 { *l = l.reversed() });
447 let k2 = Link::test_data(b).clone_and(|l| if rev2 { *l = l.reversed() });
448 let cs = k1.conn_sum(&k2);
449 let n1 = k1.n_nodes();
450 let case = format!("{a}{} # {b}{}", if rev1 { "*" } else { "" }, if rev2 { "*" } else { "" });
451
452 for (i, x) in k1.nodes().enumerate() {
453 assert_eq!(cs.node(i).incoming(), x.incoming(), "{case}: node {i} of {a}");
454 }
455 for (i, x) in k2.nodes().enumerate() {
456 assert_eq!(cs.node(n1 + i).incoming(), x.incoming(), "{case}: node {i} of {b}");
457 }
458 }
459 }
460 }
461
462 #[test]
463 fn conn_sum_of_based_summands_is_traversal_numbered() {
464 for (a, b) in [("3_1", "4_1"), ("4_1", "3_1"), ("5_1", "6_2"), ("unknot_l_twist", "3_1")] {
468 let (k1, k2) = (Link::test_data(a), Link::test_data(b));
469 assert_eq!(k1.pd_code(), k1.reindexed(1, 1).pd_code(), "{a} is not traversal-numbered");
470 assert_eq!(k2.pd_code(), k2.reindexed(1, 1).pd_code(), "{b} is not traversal-numbered");
471
472 let cs = k1.conn_sum(&k2);
473 assert_eq!(cs.base_pt(), Some(1), "{a} # {b}");
474 assert_eq!(cs.pd_code(), cs.reindexed(1, 1).pd_code(), "{a} # {b}");
475
476 let (n1, x1) = (k1.n_edges() as Edge, k1.n_nodes());
478 let (tail, head) = cs.edge_ends(1, true);
479 assert!(tail.0 >= x1 && head.0 < x1, "{a} # {b}: edge 1 must enter K1");
480 let (tail, head) = cs.edge_ends(n1 + 1, true);
481 assert!(tail.0 < x1 && head.0 >= x1, "{a} # {b}: edge {} must leave K1", n1 + 1);
482 }
483 }
484
485 #[test]
486 #[should_panic(expected = "free loops is not supported")]
487 fn conn_sum_rejects_a_free_loop_base_pt() {
488 let _ = Link::unknot().conn_sum(&Link::test_data("3_1"));
490 }
491}