yui_link/inv_link/
construct.rs1use num_integer::Integer;
6
7use crate::{Edge, InvLink, Link};
8
9impl InvLink {
10 pub fn sym_pretzel(a: i32, b: i32, c: i32) -> InvLink {
15 assert_eq!(a, c, "the strong inversion needs P(a, b, a)");
16 assert!(a % 2 != 0 && b % 2 != 0, "all-odd parameters required");
17
18 let link = Link::pretzel(a, b, c);
21 InvLink::from_symmetric_pd_code(link.pd_code())
22 }
23
24 pub fn whitehead_double(l: &InvLink, positive: bool, tw: i32) -> InvLink {
29 let base = l.base_pt().expect("companion needs a base point");
30 let cut = l.on_axis_edges().into_iter()
31 .find(|&e| e != base)
32 .expect("need a second on-axis edge for the clasp");
33 Self::whitehead_double_at(l, positive, tw, cut)
34 }
35
36 pub fn whitehead_double_at(l: &InvLink, positive: bool, tw: i32, cut: Edge) -> InvLink {
39 assert!(l.is_knot(), "the companion must be a knot");
40 assert!(l.is_strongly_invertible(), "the companion must be strongly invertible");
41 assert!(tw.is_even(), "tw must be even for a τ-symmetric diagram");
42 assert_eq!(l.inv_edge(cut), cut, "the clasp edge {cut} must be on-axis");
43
44 let base = l.base_pt().expect("companion needs a base point");
45 assert_eq!(l.inv_edge(base), base, "base point must be on-axis");
46 assert_ne!(cut, base, "the clasp cannot sit at the base point");
47
48 let half = l.writhe() + tw / 2; let inner = Link::whitehead_double_impl(l.inner(), positive, half, half, cut, Some(base));
51 Self::si_knot_from(inner)
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use itertools::Itertools;
59 use crate::misc::det;
60
61 #[test]
62 fn sym_pretzel_is_symmetric() {
63 let k = InvLink::sym_pretzel(-3, 3, -3);
65 assert!(k.is_knot());
66 assert_eq!(k.n_crossings(), 9);
67 assert_eq!(det(k.inner()), 9); assert_eq!(k.writhe(), 3); }
70
71 #[test]
72 fn whitehead_double_clasp_placement() {
73 let k = InvLink::sym_pretzel(-3, 3, -3);
77 let axis = k.on_axis_edges();
78 assert_eq!(axis.len(), 2, "a strong inversion fixes exactly two edges");
79
80 for positive in [true, false] {
81 let ds = axis.iter().map(|&base| {
84 let kb = k.clone().with_base_pt(base);
85 InvLink::whitehead_double_at(&kb, positive, 0, other(&axis, base))
86 }).collect_vec();
87 let canon = |k: &InvLink| k.inner().reindexed_canon();
88 assert_eq!(canon(&ds[0]), canon(&ds[1]), "the two clasp placements differ");
89 }
90 }
91
92 fn other(axis: &[Edge], e: Edge) -> Edge {
93 *axis.iter().find(|&&f| f != e).unwrap()
94 }
95
96 #[test]
99 fn whitehead_double_twisted() {
100 let k = InvLink::test_data("3_1");
101 assert_eq!(k.writhe(), 3);
102
103 for tw in [-2, 0, 2, 4] {
104 let w = InvLink::whitehead_double(&k, true, tw);
105 let expected = 4 * k.n_crossings() + (2 * k.writhe() + tw).unsigned_abs() as usize + 2;
106 assert_eq!(w.n_crossings(), expected, "tw = {tw}");
107 assert!(w.is_knot(), "tw = {tw}");
108 assert!(w.is_strongly_invertible(), "tw = {tw}");
109 }
110 }
111
112 #[test]
113 #[should_panic(expected = "tw must be even")]
114 fn whitehead_double_rejects_an_odd_twist() {
115 let _ = InvLink::whitehead_double(&InvLink::test_data("3_1"), true, 1);
117 }
118
119 #[test]
120 fn whitehead_double_is_symmetric() {
121 for name in ["3_1", "4_1"] {
123 let k = InvLink::test_data(name);
124 let w = InvLink::whitehead_double(&k, true, 0);
125 assert!(w.is_knot());
126 assert!(w.is_strongly_invertible());
127 let base = w.base_pt().expect("the double is based");
128 assert_eq!(w.inv_edge(base), base, "base point is on-axis");
129 let bl = (2 * k.writhe()).unsigned_abs() as usize;
131 assert_eq!(w.n_crossings(), 4 * k.n_crossings() + bl + 2);
132 }
133 }
134}