yui-link
Knots and links for the yui workspace: planar diagrams, braid words, and a couple of derived invariants. Used by yui-kh (Khovanov homology) and other knot-homology layers.
Layout
src/
├── link/
│ ├── link.rs — `Link`: the type itself — accessors, components, traversal
│ ├── link_ops.rs — operations *on* a link: mirror, connected sum, resolutions, Seifert's algorithm
│ ├── construct.rs — links built *from patterns*: twist knots, pretzels, cables, Whitehead doubles
│ ├── pd_code.rs — PD-code conversion and `Link::load`
│ ├── builder.rs — `LinkBuilder`: assemble a diagram port by port
│ ├── node.rs — `Node`: vertex of a diagram (crossing or smoothing)
│ └── path.rs — `Path`: an arc or a circle of edges
├── inv_link/
│ ├── inv_link.rs — `InvLink`: involutive link, with mirror and connected sum
│ └── construct.rs — the equivariant constructions
├── braid/
│ ├── braid.rs — `Braid`: word in the Artin generators
│ └── braid_gen.rs — `BraidGen`: signed Artin generator
├── misc/
│ └── jones.rs — `jones_polynomial`, `det`
└── test_data.rs — hard-coded PD codes / braid words (test-only)
resources/
└── inv_link/lamm/ — Lamm's tables of symmetric diagrams, for `InvLink::load`
├── A/ — one strong inversion per knot, up to 10 crossings
├── B/ — doubly transvergent diagrams, two classes `<knot>a` / `<knot>b`
└── two_bridge/ — the two-bridge knots
Key types
Link
A knot or link given by a planar diagram. Internally a Vec<Node> plus a Vec<Edge> of free loops and an optional base point. Edge is an opaque edge id (u8, or u16 under the big-link feature). Construct from:
- a PD code:
Link::from_pd_code([[1,4,2,5], ...]), - explicit nodes and loops:
Link::new(nodes, loops), or the wrapperLink::from_nodes(nodes)for no loops, - a JSON file under
<DATA_DIR>/links/:Link::load("3_1"), - predefined shapes:
Link::empty(),Link::unknot(),Link::unlink(n).
Free loops are closed components without crossings. They participate in comps(), n_comps(), n_edges(), and edges() as expected — each loop shows up as a one-element closed Path in comps().
Each link carries a base_pt: Option<Edge>, defaulting to the minimum edge of the diagram (or None for the empty link). Set explicitly via with_base_pt(e) (consuming builder; asserts e is a real edge of the diagram).
Accessors: n_crossings, n_comps, comps, writhe, loops, n_loops, n_edges, edges, base_pt, with_base_pt, is_oriented, unoriented, reindexed, reindexed_canon, traversal helpers.
Operations on a diagram: mirror, reversed (reverse the orientation; crossing signs, hence the writhe, are unchanged), conn_sum / conn_sum_at, cc_at (crossing change), resolve_at / resolve_by (Khovanov-style 0/1-smoothings), seifert_state, seifert_circles, seifert_graph.
Constructions are associated functions, taking the companion link (if any) as an argument: Link::twist_knot(n), Link::pretzel(a, b, c), Link::cable2(&l) (blackboard-framed 2-cable), Link::whitehead_double(&l, positive, tw) (tw from the Seifert framing) and Link::whitehead_double_bbf(&l, positive, tw) (from the blackboard framing).
For anything else, LinkBuilder assembles a diagram port by port; build() rejects unconnected or duplicated ports and non-planar wirings.
Node
A vertex in a planar diagram — either a crossing or a smoothing — together with an orientation:
3 2 3 2 3 2 3 2
\ / \ / \ / \_/
\ = XL, / = XR, | | = V, _ = H,
/ \ / \ / \ / \
0 1 0 1 0 1 0 1
Carries a NodeType (XL / XR / V / H), the orientation, and [Edge; 4] of incident edges in the order shown.
The four ends are named by Slot — SW, SE, NE, NW, counter-clockwise from the lower left, matching the positions 0, 1, 2, 3 above. The orientation is the pair of slots the two strands enter by, sorted, or None when the node is not coherently oriented; it is valid exactly when the two lie on different strands (orientable). A crossing's orientation survives exactly one of its two smoothings, so resolve clears it for the other.
resolve(bit) turns a crossing (XL/XR) into a smoothing (V/H); mirror() swaps XL ↔ XR; sign() returns the crossing sign as Option<Sign> (from type and orientation).
Path
An oriented connected component: either an arc (Path::Arc) or a closed loop (Path::Circ), each holding a non-empty sequence of Edges. Built with Path::arc(edges) / Path::circ(edges) (both panic on an empty sequence). Returned as the component type by Link::comps() and Link::seifert_circles(); free loops become one-element circles.
Methods: is_arc, is_circle, len, edges, contains, min_edge, end_pts (Some((first, last)) for an arc, None for a circle), into_seq. Equality is as an oriented sequence — a circle equals neither its reversal nor its rotation.
Braid
A word in the Artin generators of the braid group: a strand count plus a Vec<BraidGen>. Each BraidGen wraps a non-zero i8 whose absolute value is the (1-based) strand index and whose sign distinguishes σ_i from σ_i⁻¹. The i8 storage keeps individual generators small; it caps strand counts at i8::MAX = 127, which is well above typical use.
From<T> is implemented for BraidGen for T ∈ {i8, i16, i32, i64} (out-of-range values panic). Correspondingly, Braid::from(<[T; N]>) and Braid::from_iter::<T> accept any of those element types, so literals like Braid::from([1, 1, -2]) (default i32) work directly.
Methods: closure() -> Link (free strands become free loops in the resulting link), inv, reduced() (collapses adjacent σ σ⁻¹ pairs in a single stack pass; does not apply the braid relations), extend(by) (appends by free strands on the right), display() (ASCII rendering), is_id, Mul for concatenation, Braid::load(name) for JSON-backed braid words.
InvLink
An involutive link: a Link together with an involution on it — an edge bijection e ↦ e', the induced node bijection, and (via the inner Link) an optional axis base point. Constructors:
InvLink::new(inner, e_map)—e_map: IntoIterator<Item = (Edge, Edge)>. The constructor asserts thate_mapcovers every link edge, has image within the edge set, and is involutive.InvLink::from_symmetric_pd_code(pd_code)— for a strongly invertible knot given by a diagram based on its axis. τ reverses the traversal, so walking both ways from the base point pairs each edge with its image; no search and no relabelling is needed. The base point defaults to the least edge, which the symmetric convention puts on the axis.InvLink::si_knot_from(inner)— for a strongly invertible knot whose diagram is already based on its axis;from_symmetric_pd_codeis this applied toLink::from_pd_code.InvLink::load(name)— reads<DATA_DIR>/inv_link/<name>.json. Lamm's tables ship inlib-link/resources/inv_link/lamm/and are flattened into the data dir byscripts/fetch-knot-data.py.
with_base_pt(e) sets the base point; it asserts that e is on-axis (inv_edge(e) == e). inv_edge(e) and inv_node(x) look up the involution. reversed() reverses the orientation, leaving τ untouched — it maps edges, so a strong inversion stays one. Most read-only Link methods are delegated, including base_pt().
An InvLink is only an involutive link. on_axis_edges() lists the τ-fixed edges, and the two cases are told apart by is_strongly_invertible() (τ reverses the orientation) and is_2periodic() (τ preserves it) — both decided from the orientation, not from index arithmetic.
mirror and conn_sum / conn_sum_at are the equivariant counterparts of the Link operations — the connected sum splices along on-axis edges, conn_sum taking self's other on-axis edge and other's base point. At the Link level the sum is based on the band edge entering self, so the traversal runs through self first and the based numbering is preserved. InvLink::sym_pretzel(a, b, a) (all-odd) and InvLink::whitehead_double(&k, positive, tw) (even tw, with whitehead_double_at to choose which on-axis edge carries the clasp) are the equivariant constructions.
Derived invariants
jones_polynomial(&Link) -> LPoly<'q', i32>— Kauffman-bracket computation summing over all2^nresolutions.det(&Link) -> i32— the determinant|Δ_L(-1)| = |V_L(-1)|, read offjones_polynomial.
Conventions
- PD code. Each crossing is
[a, b, c, d]ordered counter-clockwise from the lower-left, witha → cthe incoming under-strand. See:- KnotAtlas — katlas.org/wiki/Planar_Diagrams
- KnotInfo — knotinfo.org/descriptions/pd_notation.html
- Edge ids.
Edgeisu8by default,u16under thebig-linkfeature. Only used to identify endpoint coincidence; ids don't have to be contiguous or start at 0. - Base point. Defaults to the minimum edge of the diagram. For
InvLink, the base point must be fixed by the involution (usewith_base_pt(e)to set a specific on-axis edge).
Data directory
The *::load(name) constructors read JSON files from a user-data directory — $YUI_DATA_DIR if set, otherwise the platform default (~/Library/Application Support/yui/ on macOS, ${XDG_DATA_HOME:-~/.local/share}/yui/ on Linux, %APPDATA%\yui\ on Windows). To populate it from the KnotInfo database:
The script writes per-knot PD codes to <DATA_DIR>/links/, braid words to <DATA_DIR>/braid/, and flattens this crate's bundled Lamm tables (resources/inv_link/lamm/**) into <DATA_DIR>/inv_link/. Pass --clean to drop entries that were removed from the repo.
Quick example
use Link;
// Trefoil knot from a PD code.
let l = from_pd_code;
assert_eq!;
assert_eq!;
assert_eq!;
// Free loops via `unlink(n)`.
let u = unlink;
assert_eq!;
assert_eq!;
Feature flags
test-utils— exposes the hard-codedLink::test_data(name)/Braid::test_data(name)/InvLink::test_data(name)constructors to downstream crates (behind#[cfg(test)]by default).big-link— widensEdgefromu8tou16and the resolutionStatefrom 64 to 128 bits, raising the crossing limit from 64 to 128.
License
This library is licensed under the MIT License.
This README was generated by Claude.