use petgraph::Graph;
use yui_core::num::Sign;
use yui_core::ext::CloneAnd;
use yui_core::bitseq::Bit;
use super::{Edge, Link, LinkBuilder, NodeType, Path, Slot, State};
impl Link {
pub fn conn_sum(&self, other: &Link) -> Link {
let self_e = self.base_pt().expect("self needs a base point");
let other_e = other.base_pt().expect("other needs a base point");
self.conn_sum_at(other, self_e, other_e)
}
pub fn conn_sum_at(&self, other: &Link, self_e: Edge, other_e: Edge) -> Link {
assert!(self.is_oriented(), "conn_sum requires an oriented link (self)");
assert!(other.is_oriented(), "conn_sum requires an oriented link (other)");
assert!(
!self.loops().contains(&self_e) && !other.loops().contains(&other_e),
"connected sum on free loops is not supported yet"
);
let mut b = LinkBuilder::new();
let v1 = b.add_link(self);
let v2 = b.add_link(other);
let port = |verts: &[_], (i, s): (usize, Slot)| (verts[i], s.index());
let (t1, h1) = self.edge_ends(self_e, true);
let (t2, h2) = other.edge_ends(other_e, true);
let (t1, h1) = (port(&v1, t1), port(&v1, h1));
let (t2, h2) = (port(&v2, t2), port(&v2, h2));
b.disconnect(h1); b.disconnect(h2); b.connect(t1, h2); b.connect(t2, h1);
let base = self.base_pt().map(|e|
if e != self_e {
b.edge_at(port(&v1, self.edge_ends(e, false).0)).unwrap()
} else {
b.edge_at(h1).unwrap()
}
);
let n1 = self.n_nodes();
let sum = b.build_with(|i, s| {
let (l, i) = if i < n1 { (self, i) } else { (other, i - n1) };
l.node(i).is_incoming(Slot::from(s))
}).unwrap();
match base {
Some(e) => sum.with_base_pt(e),
None => sum,
}
}
pub fn mirror(&self) -> Self {
let l = Self::new(
self.nodes().map(|x| x.mirror()),
self.loops().iter().copied(),
);
match self.base_pt() {
Some(e) => l.with_base_pt(e),
None => l,
}
}
pub fn reversed(&self) -> Self {
let l = Self::new(
self.nodes().map(|x| x.reversed()),
self.loops().iter().copied(),
);
match self.base_pt() {
Some(e) => l.with_base_pt(e),
None => l,
}
}
pub fn cc_at(&self, i: usize) -> Self {
assert!(self.node(i).is_crossing());
self.clone_and(|l|
*l.node_mut(i) = l.node(i).mirror()
)
}
pub fn resolve_at(&self, i: usize, r: Bit) -> Self {
assert!(self.node(i).is_crossing());
self.clone_and(|l| {
*l.node_mut(i) = l.node(i).resolve(r);
l.normalize_ori();
})
}
pub fn resolve_by(&self, s: &State) -> Self {
assert!(s.len() == self.n_crossings());
let n = self.n_nodes();
let itr = (0..n).filter(|&i| self.node(i).is_crossing());
self.clone_and(|l| {
for (i, r) in Iterator::zip(itr, s.iter()) {
*l.node_mut(i) = self.node(i).resolve(r);
}
l.normalize_ori();
})
}
pub fn seifert_state(&self) -> State {
assert!(self.is_oriented());
let seq = self.crossings().map(|x|
match x.sign() {
Some(Sign::Pos) => 0,
Some(Sign::Neg) => 1,
None => panic!("Impossible.")
}
);
State::from_iter(seq)
}
pub fn seifert_circles(&self) -> Vec<Path> {
self.resolve_by(&self.seifert_state()).comps()
}
pub fn seifert_graph(&self) -> Graph<Path, usize> {
assert!(self.is_oriented());
type G = Graph<Path, usize>;
let s0 = self.seifert_state();
let l0 = self.resolve_by(&s0);
let mut graph = Graph::new();
for c in l0.comps() {
graph.add_node(c);
}
let find_node = |graph: &G, e| {
graph.node_indices().find(|&i|
graph[i].contains(e)
)
};
for (i, x) in l0.nodes().enumerate() {
let (e1, e2) = if x.node_type() == NodeType::V {
(x.edge(Slot::SW), x.edge(Slot::SE))
} else {
(x.edge(Slot::SW), x.edge(Slot::NE))
};
let n1 = find_node(&graph, e1).unwrap();
let n2 = find_node(&graph, e2).unwrap();
graph.add_edge(n1, n2, i);
}
graph
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Braid, Node};
use crate::NodeType::{XL, XR};
use crate::misc::jones_polynomial;
#[test]
fn link_reversed() {
let l = Link::test_data("3_1");
let r = l.reversed();
assert!(r.is_oriented());
assert_eq!(r.writhe(), l.writhe(), "reversing a knot keeps every crossing sign");
assert_eq!(r.base_pt(), l.base_pt());
for (x, y) in Iterator::zip(l.nodes(), r.nodes()) {
assert_eq!(y.node_type(), x.node_type());
assert_eq!(y.edges(), x.edges());
}
assert_eq!(r.reversed(), l, "reversing twice is the identity");
for (x, y) in Iterator::zip(l.nodes(), r.nodes()) {
let (p, q) = x.incoming().unwrap();
assert_eq!(y.incoming(), Some((x.paired_slot(p).min(x.paired_slot(q)),
x.paired_slot(p).max(x.paired_slot(q)))));
}
}
#[test]
fn link_mirror() {
let l = Link::test_data("unknot_l_twist");
assert_eq!(l.node(0).node_type(), XL);
let l = l.mirror();
assert_eq!(l.node(0).node_type(), XR);
}
#[test]
fn mirror_preserves_loops() {
let l = Link::unlink(1).mirror();
assert_eq!(l.n_loops(), 1);
assert_eq!(l.loops(), &[1]);
}
#[test]
fn mirror_preserves_base_pt() {
let l = Link::test_data("3_1").with_base_pt(3).mirror();
assert_eq!(l.base_pt(), Some(3));
}
#[test]
fn crossing_change() {
let l = Link::test_data("3_1");
let l2 = l.cc_at(1);
assert_eq!(l.node(1), &Node::new(XL, Some((Slot::SW, Slot::NW)), [3,1,4,6]));
assert_eq!(l2.node(1), &Node::new(XR, Some((Slot::SW, Slot::NW)), [3,1,4,6]));
}
#[test]
fn resolve_drops_the_orientation_wholesale() {
let l = Link::test_data("3_1");
assert_eq!(l.seifert_state(), State::from([0, 0, 0]));
assert!(l.resolve_by(&State::from([0, 0, 0])).is_oriented());
for st in [[0, 0, 1], [0, 1, 1], [1, 1, 1]] {
let r = l.resolve_by(&State::from(st));
assert!(!r.is_oriented(), "{st:?} left a partial orientation");
r.verify_ori();
}
}
#[test]
fn link_resolve() {
let s = State::from([0, 0, 0]);
let l = Link::test_data("3_1").resolve_by(&s);
let comps = l.comps();
assert_eq!(comps.len(), 2);
assert!(comps.iter().all(|c| c.is_circle()));
let s = State::from([1, 1, 1]);
let l = Link::test_data("3_1").resolve_by(&s);
let comps = l.comps();
assert_eq!(comps.len(), 3);
assert!(comps.iter().all(|c| c.is_circle()));
}
#[test]
fn seifert_graph_trefoil() {
let l = Link::test_data("3_1");
let g = l.seifert_graph();
assert_eq!(g.node_count(), 2);
assert_eq!(g.edge_count(), 3);
}
#[test]
fn seifert_graph_unlink() {
let l = Link::unlink(3);
let g = l.seifert_graph();
assert_eq!(g.node_count(), 3);
assert_eq!(g.edge_count(), 0);
}
#[test]
fn conn_sum_is_jones_multiplicative() {
let k1 = Link::test_data("3_1");
let k2 = Link::test_data("4_1");
let cs = k1.conn_sum(&k2); assert_eq!(cs.n_comps(), 1);
assert_eq!(cs.n_crossings(), k1.n_crossings() + k2.n_crossings());
assert!(cs.is_oriented());
let (vcs, vu) = (jones_polynomial(&cs), jones_polynomial(&Link::unknot()));
let (v1, v2) = (jones_polynomial(&k1), jones_polynomial(&k2));
assert_eq!(&vcs * &vu, &v1 * &v2);
let cs2 = k1.conn_sum_at(&k2, 4, 6);
assert_eq!(jones_polynomial(&cs2), jones_polynomial(&cs));
}
#[test]
fn conn_sum_of_braid_closures() {
let k1 = Braid::from([1, 1, 1]).closure(); let k2 = Braid::from([1, -2, 1, -2]).closure(); let cs = k1.conn_sum(&k2);
assert_eq!(cs.n_comps(), 1);
assert!(cs.is_oriented());
let (vcs, vu) = (jones_polynomial(&cs), jones_polynomial(&Link::unknot()));
let (v1, v2) = (jones_polynomial(&k1), jones_polynomial(&k2));
assert_eq!(&vcs * &vu, &v1 * &v2);
}
#[test]
fn conn_sum_of_two_curls() {
let k = Link::test_data("unknot_l_twist");
let cs = k.conn_sum(&k);
assert_eq!(cs.pd_code(), [[1,3,2,2],[3,1,4,4]]);
assert_eq!(cs.base_pt(), Some(1));
}
#[test]
fn conn_sum_of_a_curl_and_its_reverse() {
let k = Link::test_data("unknot_l_twist");
let cs = k.conn_sum(&k.reversed());
assert_eq!(cs.pd_code(), [[1,3,2,2],[4,4,1,3]]);
assert_eq!(cs.base_pt(), Some(1));
}
#[test]
fn conn_sum_of_a_curl_and_its_mirror() {
let k = Link::test_data("unknot_l_twist");
let cs = k.conn_sum(&k.mirror());
assert_eq!(cs.pd_code(), [[1,3,2,2],[4,3,1,4]]);
assert_eq!(cs.base_pt(), Some(1));
}
#[test]
fn conn_sum_of_a_curl_and_its_concordance_inverse() {
let k = Link::test_data("unknot_l_twist");
let cs = k.conn_sum(&k.mirror().reversed());
assert_eq!(cs.pd_code(), [[1,3,2,2],[3,4,4,1]]);
assert_eq!(cs.base_pt(), Some(1));
}
#[test]
fn conn_sum_at_of_two_curls_away_from_the_base_pt() {
let k = Link::test_data("unknot_l_twist");
let cs = k.conn_sum_at(&k, 2, 2);
assert_eq!(cs.pd_code(), [[1,1,4,2],[3,3,2,4]]);
assert_eq!(cs.base_pt(), Some(1));
assert_eq!(cs.reindexed(1, 1).pd_code(), Link::test_data("unknot_l_twist2").pd_code());
}
#[test]
fn conn_sum_at_of_two_curls_on_different_edges() {
let k = Link::test_data("unknot_l_twist");
let cs = k.conn_sum_at(&k, 2, 1);
assert_eq!(cs.pd_code(), [[1,1,3,2],[3,2,4,4]]);
assert_eq!(cs.base_pt(), Some(1));
let expected = Link::from_pd_code([[1,1,2,3],[2,3,4,4]]);
assert_eq!(cs.reindexed(1, 1).pd_code(), expected.reindexed(1, 1).pd_code());
}
#[test]
fn dms_construction() {
let k = Link::from_pd_code([[1,4,2,5],[5,2,6,3],[3,6,4,1]]);
let mk = k.mirror();
let k2 = k.conn_sum(&k);
assert_eq!(k2.pd_code(), Link::from_pd_code([
[1,4,2,5],[5,2,6,3],[3,6,4,7],[7,10,8,11],[11,8,12,9],[9,12,10,1],
]).pd_code());
assert_eq!(k2.base_pt(), Some(1));
let with_k = k2.conn_sum_at(&k, 7, 1);
assert_eq!(with_k.pd_code(), Link::from_pd_code([
[1,4,2,5],[5,2,6,3],[3,6,4,13],[7,10,8,11],[11,8,12,9],[9,12,10,1],
[13,16,14,17],[17,14,18,15],[15,18,16,7],
]).pd_code());
assert_eq!(with_k.base_pt(), Some(1));
let with_mk = k2.conn_sum_at(&mk, 7, 1);
assert_eq!(with_mk.pd_code(), Link::from_pd_code([
[1,4,2,5],[5,2,6,3],[3,6,4,13],[7,10,8,11],[11,8,12,9],[9,12,10,1],
[16,14,17,13],[14,18,15,17],[18,16,7,15],
]).pd_code());
assert_eq!(with_mk.base_pt(), Some(1));
let dms = with_mk.conn_sum_at(&mk.reversed(), 16, 1);
assert_eq!(dms.pd_code(), Link::from_pd_code([
[1,4,2,5],[5,2,6,3],[3,6,4,13],[7,10,8,11],[11,8,12,9],[9,12,10,1],
[16,14,17,13],[14,18,15,17],[18,19,7,15],
[19,21,24,22],[21,23,20,24],[23,16,22,20],
]).pd_code());
assert_eq!(dms.base_pt(), Some(1));
}
#[test]
fn conn_sum_keeps_each_summand_orientation() {
for (a, b) in [("3_1", "4_1"), ("4_1", "3_1"), ("unknot_l_twist", "3_1"), ("6_2", "5_1")] {
for (rev1, rev2) in [(false, false), (true, false), (false, true), (true, true)] {
let k1 = Link::test_data(a).clone_and(|l| if rev1 { *l = l.reversed() });
let k2 = Link::test_data(b).clone_and(|l| if rev2 { *l = l.reversed() });
let cs = k1.conn_sum(&k2);
let n1 = k1.n_nodes();
let case = format!("{a}{} # {b}{}", if rev1 { "*" } else { "" }, if rev2 { "*" } else { "" });
for (i, x) in k1.nodes().enumerate() {
assert_eq!(cs.node(i).incoming(), x.incoming(), "{case}: node {i} of {a}");
}
for (i, x) in k2.nodes().enumerate() {
assert_eq!(cs.node(n1 + i).incoming(), x.incoming(), "{case}: node {i} of {b}");
}
}
}
}
#[test]
fn conn_sum_of_based_summands_is_traversal_numbered() {
for (a, b) in [("3_1", "4_1"), ("4_1", "3_1"), ("5_1", "6_2"), ("unknot_l_twist", "3_1")] {
let (k1, k2) = (Link::test_data(a), Link::test_data(b));
assert_eq!(k1.pd_code(), k1.reindexed(1, 1).pd_code(), "{a} is not traversal-numbered");
assert_eq!(k2.pd_code(), k2.reindexed(1, 1).pd_code(), "{b} is not traversal-numbered");
let cs = k1.conn_sum(&k2);
assert_eq!(cs.base_pt(), Some(1), "{a} # {b}");
assert_eq!(cs.pd_code(), cs.reindexed(1, 1).pd_code(), "{a} # {b}");
let (n1, x1) = (k1.n_edges() as Edge, k1.n_nodes());
let (tail, head) = cs.edge_ends(1, true);
assert!(tail.0 >= x1 && head.0 < x1, "{a} # {b}: edge 1 must enter K1");
let (tail, head) = cs.edge_ends(n1 + 1, true);
assert!(tail.0 < x1 && head.0 >= x1, "{a} # {b}: edge {} must leave K1", n1 + 1);
}
}
#[test]
#[should_panic(expected = "free loops is not supported")]
fn conn_sum_rejects_a_free_loop_base_pt() {
let _ = Link::unknot().conn_sum(&Link::test_data("3_1"));
}
}