1use itertools::Itertools;
18
19use super::{Edge, Link, Node, NodeType};
20
21pub type PDCodeX = [Edge; 4];
23
24impl Link {
25 pub fn from_pd_code<I>(pd_code: I) -> Self
26 where I: IntoIterator<Item = PDCodeX> {
27 let nodes = pd_code.into_iter().map(Node::from_pd_code).collect_vec();
28 let mut l = Self::from_nodes(nodes); l.reorient(|_, s| s.index() == 0); l
31 }
32
33 pub fn pd_code(&self) -> Vec<PDCodeX> {
38 assert!(self.loops().is_empty(), "a PD code cannot represent free loops");
39
40 let mut pd = Vec::with_capacity(self.n_nodes());
41 self.traverse_comps(|_, i, j| {
42 let x = self.node(i);
43 let under = match x.node_type() {
46 NodeType::XL => j.index() % 2 == 0,
47 NodeType::XR => j.index() % 2 == 1,
48 _ => return,
49 };
50 if under {
51 pd.push([x.edge(j), x.edge(j.shift(1)), x.edge(j.shift(2)), x.edge(j.shift(3))]);
52 }
53 });
54 pd
55 }
56
57 pub fn load(name: &str) -> Result<Link, Box<dyn std::error::Error>> {
58 let json = yui_core::util::data_dir::load_json("links", name)?;
59 let data: Vec<PDCodeX> = serde_json::from_str(&json)?;
60 Ok(Link::from_pd_code(data))
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::NodeType::XL;
68
69 #[test]
70 fn pd_code_roundtrip() {
71 use crate::misc::jones_polynomial;
72
73 let l = Link::test_data("3_1"); let l2 = Link::from_pd_code(l.pd_code());
77 assert_eq!(jones_polynomial(&l), jones_polynomial(&l2), "XL round-trip");
78
79 let m = l.mirror();
81 let m2 = Link::from_pd_code(m.pd_code());
82 assert_eq!(jones_polynomial(&m), jones_polynomial(&m2), "XR round-trip");
83
84 assert_ne!(jones_polynomial(&l), jones_polynomial(&m));
86 }
87
88 #[test]
89 fn pd_code_depends_only_on_the_diagram() {
90 for name in ["3_1", "4_1", "5_2", "6_1", "L2a1", "L4a1"] {
92 let l = Link::test_data(name);
93 assert_eq!(Link::from_pd_code(l.pd_code()).pd_code(), l.pd_code(), "{name}");
94 }
95 let l = Link::pretzel(1, 3, 5);
96 assert_eq!(Link::from_pd_code(l.pd_code()).pd_code(), l.pd_code(), "pretzel(1,3,5)");
97 }
98
99 #[test]
100 #[should_panic(expected = "cannot represent free loops")]
101 fn pd_code_rejects_free_loops() {
102 let _ = Link::unknot().pd_code();
103 }
104
105 #[test]
106 fn link_from_pd_code() {
107 let l = Link::test_data("unknot_l_twist");
108 assert_eq!(l.n_nodes(), 1);
109 assert_eq!(l.node(0).node_type(), XL);
110 }
111}