1use std::collections::HashSet;
5use petgraph::stable_graph::NodeIndex;
6use crate::{Link, Edge, LinkBuilder, Port};
7
8impl Link {
9 pub fn twist_knot(n: i32) -> Link {
13 use crate::NodeType::{XL, XR};
14 let mut b = LinkBuilder::new();
15
16 let (v0, v1, v2, v3) = b.add_v_twist(XR, 2);
18
19 if n == 0 {
20 b.connect(v3, v2);
21 b.connect(v0, v1); return b.build().unwrap();
23 }
24
25 let tt = if n >= 0 { XR } else { XL };
27 let (h0, h1, h2, h3) = b.add_h_twist(tt, n.unsigned_abs() as usize);
28
29 b.connect(v0, h3);
30 b.connect(v3, h0);
31 b.connect(v1, h2);
32 b.connect(v2, h1);
33 b.build().unwrap()
34 }
35
36 pub fn pretzel(a: i32, b: i32, c: i32) -> Link {
41 use crate::NodeType::{XL, XR};
42 assert!(a != 0 && b != 0 && c != 0, "pretzel parameters must be nonzero");
43
44 let mut bld = LinkBuilder::new();
45 let ends: Vec<_> = [a, b, c].iter().map(|&v| {
46 let ty = if v > 0 { XR } else { XL };
47 bld.add_v_twist(ty, v.unsigned_abs() as usize) }).collect();
49 let (sw1, se1, ne1, nw1) = ends[0];
50 let (sw2, se2, ne2, nw2) = ends[1];
51 let (sw3, se3, ne3, nw3) = ends[2];
52
53 bld.connect(ne1, nw2);
54 bld.connect(ne2, nw3);
55 bld.connect(se1, sw2);
56 bld.connect(se2, sw3);
57 bld.connect(nw1, ne3); bld.connect(sw1, se3); let start = bld.edge_at(nw1).unwrap();
61 bld.build().expect("pretzel must be planar").reindexed(start, 1)
62 }
63
64 pub fn cable2(l: &Link) -> Link {
68 let (b, cab) = Self::cable2_builder(l);
69
70 let incoming: HashSet<Port> = l.nodes().enumerate().flat_map(|(i, x)| {
73 let (p, q) = x.incoming().expect("cable2 needs an oriented diagram");
74 [p, q].map(|s| cab[i][s.index()])
75 }).flat_map(|(a0, a1)|
76 [a0, a1]
77 ).collect();
78
79 b.build_with(|i, s| incoming.contains(&(NodeIndex::new(i), s))).unwrap()
80 }
81
82 fn cable2_builder(l: &Link) -> (LinkBuilder, Vec<[(Port, Port); 4]>) {
85 let mut b = LinkBuilder::new();
86
87 let cab: Vec<[(Port, Port); 4]> = l.nodes().map(|x| {
89 let t = x.node_type();
90 let [s0, s1, s2, s3] = [b.add_node(t), b.add_node(t), b.add_node(t), b.add_node(t)];
91 b.connect((s0, 2), (s1, 0));
92 b.connect((s0, 3), (s2, 1));
93 b.connect((s1, 3), (s3, 1));
94 b.connect((s2, 2), (s3, 0));
95 [((s2, 0), (s0, 0)), ((s0, 1), (s1, 1)), ((s1, 2), (s3, 2)), ((s3, 3), (s2, 3))]
97 }).collect();
98
99 for e in l.edges() {
102 let ((i, s), (j, t)) = l.edge_ends(e, false);
103 let ((a0, a1), (b0, b1)) = (cab[i][s.index()], cab[j][t.index()]);
104 b.connect(a0, b1);
105 b.connect(a1, b0);
106 }
107 for _ in l.loops() { b.add_loop();
109 b.add_loop();
110 }
111 (b, cab)
112 }
113
114 pub fn whitehead_double(l: &Link, positive: bool, tw: i32) -> Link {
117 Self::whitehead_double_bbf(l, positive, 2 * l.writhe() + tw)
118 }
119
120 pub fn whitehead_double_bbf(l: &Link, positive: bool, tw: i32) -> Link {
123 let cut = l.edges().into_iter()
125 .find(|&e| {
126 let ((i, _), (j, _)) = l.edge_ends(e, false);
127 i != j
128 })
129 .expect("the companion needs an edge joining two distinct crossings");
130 Self::whitehead_double_impl(l, positive, 0, tw, cut, None)
131 }
132
133 pub(crate) fn whitehead_double_impl(l: &Link, positive: bool, tw_a: i32, tw_b: i32, cut: Edge, base: Option<Edge>) -> Link {
137 use crate::NodeType::{XL, XR};
138 assert!(l.is_knot(), "the Whitehead double requires a knot companion");
139 assert!(base.is_none_or(|b| b != cut), "the base point must be away from the cut");
140
141 let (mut b, cab) = Self::cable2_builder(l);
142
143 let ((ia, sa), (ib, sb)) = l.edge_ends(cut, false);
146 let ((a0, a1), (b0, b1)) = (cab[ia][sa.index()], cab[ib][sb.index()]);
147 b.disconnect(a0); b.disconnect(a1);
149
150 let twist_type = |tw: i32| if tw >= 0 { XR } else { XL };
161 let (mut upper, mut lower) = (a1, a0);
162
163 if tw_a != 0 {
164 let (ll, lr, ur, ul) = b.add_h_twist(twist_type(tw_a), tw_a.unsigned_abs() as usize);
165 b.connect(upper, ul);
166 b.connect(lower, ll);
167 (upper, lower) = (ur, lr);
168 }
169
170 let ct = if positive { XL } else { XR };
172 let (ll, lr, ur, ul) = b.add_v_twist(ct, 2);
173 b.connect(upper, ul);
174 b.connect(lower, ll);
175 (upper, lower) = (ur, lr);
176
177 if tw_b != 0 {
178 let (ll, lr, ur, ul) = b.add_h_twist(twist_type(tw_b), tw_b.unsigned_abs() as usize);
179 b.connect(upper, ul);
180 b.connect(lower, ll);
181 (upper, lower) = (ur, lr);
182 }
183
184 b.connect(upper, b0);
185 b.connect(lower, b1);
186
187 let base_pt = base.map(|e| {
190 let ((i, s), _) = l.edge_ends(e, false);
191 b.edge_at(cab[i][s.index()].0).unwrap()
192 });
193
194 let double = b.build().unwrap();
195 match base_pt {
196 Some(e) => double.with_base_pt(e),
197 None => double,
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::Braid;
206 use crate::misc::{jones_polynomial, det};
207
208 fn same_knot(a: &Link, b: &Link) -> bool {
209 let ja = jones_polynomial(a);
210 ja == jones_polynomial(b) || ja == jones_polynomial(&b.mirror())
211 }
212
213 #[test]
214 fn braid_closures_are_jones_faithful() {
215 assert!(same_knot(&Braid::from([1, 1, 1]).closure(), &Link::test_data("3_1")));
216 assert!(same_knot(&Braid::from([1, -2, 1, -2]).closure(), &Link::test_data("4_1")));
217 }
218
219 #[test]
220 fn twist_knot_matches_references() {
221 let table = ["3_1", "4_1", "5_2", "6_1", "7_2"];
224 for (i, name) in table.iter().enumerate() {
225 let n = i as i32 + 1;
226 assert!(same_knot(&Link::twist_knot(n), &Link::test_data(name)), "twist_knot({n}) ≠ {name}");
227 assert!(same_knot(&Link::twist_knot(-1 - n), &Link::test_data(name)), "twist_knot({}) ≠ mirror {name}", -1 - n);
228 }
229 }
230
231
232 #[test]
233 fn twist_knot_determinants() {
234 for n in 0..=6 {
237 let k = Link::twist_knot(n);
238 assert_eq!(k.n_comps(), 1);
239 assert!(k.is_oriented());
240 assert_eq!(det(&k), 2 * n + 1, "det twist_knot({n})");
241 assert_eq!(det(&Link::twist_knot(-1 - n)), 2 * n + 1, "det twist_knot({})", -1 - n);
242 }
243 }
244
245 #[test]
246 fn pretzel_determinants() {
247 for (a, b, c) in [(1, 1, 1), (-1, -1, -1), (1, 3, 5), (-2, 3, 7), (2, 2, 2), (2, 2, 3), (2, 2, -3)] {
252 let l = Link::pretzel(a, b, c);
253 let n = (a.unsigned_abs() + b.unsigned_abs() + c.unsigned_abs()) as usize;
254 assert_eq!(l.n_crossings(), n, "P({a},{b},{c}) crossing count");
255 assert_eq!(det(&l), (a * b + b * c + c * a).abs(), "det P({a},{b},{c})");
256 assert_eq!(l.edges(), (1..=l.n_edges() as Edge).collect::<Vec<_>>(), "P({a},{b},{c}) numbering");
257 }
258 }
259
260 #[test]
261 fn pretzel_band_symmetries() {
262 let canon = |l: &Link| {
268 let rev = Link::from_pd_code(l.pd_code().into_iter().map(|[a, b, c, d]| [c, d, a, b]));
270 l.reindexed_canon().pd_code().min(rev.reindexed_canon().pd_code())
271 };
272
273 for (a, b, c) in [(1, 3, 5), (3, 5, 7), (-3, 3, -3), (-2, 3, 7), (-5, 5, -5)] {
274 let p = canon(&Link::pretzel(a, b, c));
275 assert_eq!(canon(&Link::pretzel(b, c, a)), p, "cyclic P({a},{b},{c})");
276 assert_eq!(canon(&Link::pretzel(c, a, b)), p, "cyclic P({a},{b},{c})");
277 assert_eq!(canon(&Link::pretzel(c, b, a)), p, "reversal P({a},{b},{c})");
278 }
279 }
280
281 #[test]
282 fn mirror_identities() {
283 for (a, b, c) in [(1, 3, 5), (3, 5, 7), (-2, 3, 7), (-5, 5, -5)] {
285 assert_eq!(Link::pretzel(a, b, c).mirror().pd_code(), Link::pretzel(-a, -b, -c).pd_code(),
286 "mirror P({a},{b},{c})");
287 }
288 for name in ["3_1", "4_1", "5_2"] {
289 let k = Link::test_data(name);
290 assert_eq!(Link::cable2(&k).mirror().pd_code(), Link::cable2(&k.mirror()).pd_code(),
291 "mirror cable2({name})");
292 assert_eq!(Link::whitehead_double(&k, true, 0).mirror().pd_code(),
293 Link::whitehead_double(&k.mirror(), false, 0).pd_code(),
294 "mirror D+({name}) vs D-(mirror {name})");
295 }
296 }
297
298 #[test]
299 fn pretzel_trefoil() {
300 assert!(same_knot(&Link::pretzel(1, 1, 1), &Link::test_data("3_1")));
301 }
302
303 #[test]
304 #[ignore = "slow: det sums over all 2^n resolutions, and the framing test needs 14-crossing doubles"]
305 fn whitehead_double_of_unknot() {
306 for word in [vec![1, -2], vec![1, 2], vec![-1, -2]] {
308 let u = Braid::from_iter(word).closure();
309 for positive in [true, false] {
310 let d = Link::whitehead_double(&u, positive, 0);
311 assert_eq!(d.n_comps(), 1);
312 assert_eq!(det(&d), 1, "D±(unknot) must be the unknot (trivial Alexander)");
313 }
314 }
315 }
316
317 #[test]
318 fn whitehead_double_is_a_knot() {
319 for name in ["3_1", "4_1", "5_2", "6_1"] {
323 let k = Link::test_data(name);
324 let expected = 4 * k.n_crossings() + 2 * k.writhe().unsigned_abs() as usize + 2;
325 for positive in [true, false] {
326 let d = Link::whitehead_double(&k, positive, 0);
327 assert_eq!(d.n_comps(), 1, "D±({name}) is a knot");
328 assert_eq!(d.n_crossings(), expected, "D±({name}) crossing count");
329 assert!(d.is_oriented());
330 }
331 }
332 }
333
334 #[test]
335 fn whitehead_double_clasp_sign() {
336 let u = Braid::from([1, -2]).closure(); for (tw, pos_det, neg_det) in [(1, 1, 3), (2, 3, 5)] {
342 assert_eq!(det(&Link::whitehead_double(&u, true, tw)), pos_det, "D+(U, tw={tw})");
343 assert_eq!(det(&Link::whitehead_double(&u, false, tw)), neg_det, "D-(U, tw={tw})");
344 }
345 }
346
347 #[test]
348 fn cable2_trefoil() {
349 let k = Link::test_data("3_1");
350 let c = Link::cable2(&k);
351 assert_eq!(c.n_crossings(), 4 * k.n_crossings());
352 assert_eq!(c.n_comps(), 2, "2-cable of a knot is a 2-component link");
353 assert!(c.is_oriented());
354 let _ = c.seifert_circles(); }
356
357 #[test]
358 fn cable2_is_parallel() {
359 for name in ["3_1", "5_1", "5_2"] {
362 let k = Link::test_data(name);
363 let c = Link::cable2(&k);
364 assert_eq!(c.writhe(), 4 * k.writhe(), "cable2({name}) is not parallel");
365 }
366 }
367
368 #[test]
369 fn cable2_is_hopf() {
370 let c = Link::cable2(&Link::test_data("unknot_l_twist"));
372 assert_eq!(c.n_comps(), 2);
373 assert_ne!(jones_polynomial(&c), jones_polynomial(&Link::unlink(2)),
374 "2-cable of a framed unknot must be linked (Hopf), not the unlink");
375 }
376
377}