Interactive demo and research code for distinguishing prime knots using quandle coloring and quandle 2-cocycle invariants. Draw or paste a knot diagram, and the app identifies it by its coloring signature.
Live demo: https://knot.cc.cd
This repository accompanies a research project (Oct 2025 – Feb 2026) at Guangdong Technion - Israel Institute of Technology (GTIIT), supervised by Prof. Marco Farinati. It implements the full experimental pipeline: encode KnotInfo diagrams, count quandle colorings, select a small distinguishing family of quandles via minimum set cover, and refine it with cocycle invariants.
The census of 84 prime knots with fewer than 10 crossings (from KnotInfo) is evaluated against a pool of 2,947 finite quandles.
| Result | Value |
|---|---|
| Prime knots (crossing number < 10) | 84 |
| Candidate quandles | 2,947 |
| Knot pairs distinguished by coloring invariants | 3,484 of 3,486 |
| Minimum coloring set (minimum set cover) | 14 quandles |
| Refined set after adding 2-cocycle invariants | 9 quandles |
| Pairs still unresolved | (7₄, 9₂) and (8₁₄, 9₈) |
Every number above is recomputed by a command in Reproducing the results, from the data and result files committed in this repository.
The 14-quandle minimum coloring set (tools/io/minimal_set_raw.txt):
Dihedral(27), Alexander(ℤ₂₉, t=3), Dihedral(58), Alexander(ℤ₆₉, t=20), Dihedral(85), Alexander(ℤ₉₁, t=6), Alexander(ℤ₉₁, t=18), Alexander(ℤ₉₁, t=48), Dihedral(92), Alexander(ℤ₉₅, t=17), Alexander(𝔽₄, t=α), Alexander(ℤ₃[i], t=i), Alexander(ℤ₇[i], t=i), Connected Quandle 6₁.
The refined 9-quandle set after cocycle invariants (tools/io/minimal_set.txt):
Dihedral(27), Alexander(ℤ₂₉, t=3), Alexander(ℤ₆₉, t=20), Dihedral(85), Alexander(ℤ₉₁, t=48), Dihedral(92), Alexander(ℤ₉₅, t=17), Alexander(𝔽₄, t=α), Connected Quandle 6₁.
Of the 14, five quandles (Dihedral(58), Alexander(ℤ₉₁,t=6), Alexander(ℤ₉₁,t=18), Alexander(ℤ₃[i],t=i), Alexander(ℤ₇[i],t=i)) are dropped because every knot pair they uniquely witnessed is also distinguished by another retained quandle or by a cocycle invariant.
Tested with Python 3.9.6 on macOS 26 (Apple Silicon, no GPU required).
git clone https://github.com/mikessslx/knot-studio.git
cd knot-studio
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtThe 84-knot census is committed under data/, so the app works immediately after cloning:
PUBLIC_DEMO_MODE=0 python3 app.pyOpen http://127.0.0.1:5001. PUBLIC_DEMO_MODE=0 enables the CPU-heavy endpoints (coloring, cocycle, turbo); the public deployment sets it to 1.
In the UI, draw a diagram or load a preset (0_1, 3_1, 4_1, 5_1), then use Recognize Knot to turn strokes into arcs and crossings and Identify Knot to match against the signature database. Identification handles single-component knots with at most 9 crossings, including connected sums summing to at most 9.
Docker also works:
cp .env.example .env # optionally set PROJECT_REPO_URL
docker compose up --build| Path | Contents |
|---|---|
data/ |
84 knot JSON files, one per prime knot with < 10 crossings |
tools/io/knotinfo.csv |
Source PD notation, 249 rows (the 84 knots used here, plus 165 ten-crossing knots) |
Both derive from KnotInfo (Livingston & Moore, https://knotinfo.org), which is publicly available; please cite KnotInfo if you reuse the data. tools/knotinfo.py regenerates data/ from tools/io/knotinfo.csv.
Each knot JSON uses a signed crossing-arc encoding:
{
"name": "7_4",
"arcs": [0, 1, 2, 3, 4, 5, 6],
"crossings": [
{"id": 0, "type": "positive", "over": 4, "under_in": 0, "under_out": 1}
]
}type is the crossing sign; over is the arc passing over; under_in / under_out are the incoming and outgoing under-arcs (following the knot's orientation).
All commands are run from the repository root with the virtualenv active. Committed result files are in tools/io/; regenerate them to verify.
| Result | Command | Runtime |
|---|---|---|
| All coloring counts (84 knots × 2,947 quandles) | python3 tools/turbo_cli.py --data-dir data --max-crossings 9 -o tools/io/turbo_results.csv --overwrite |
~18 s |
| Distinguished / unresolved pairs (3,484 and the 2 pairs) | python3 tools/results.py |
<1 s |
| Minimum 14-quandle set (minimum set cover) | python3 tools/minimal_set.py --solver ortools --max 1 |
prints 14 in ~7 s; enumerating all optima runs much longer |
| Refine 14 → 9 with cocycle invariants | python3 tools/compare_minimal_set.py |
~1 min |
Notes:
- The first command reproduces
tools/io/turbo_results.csvexactly (verified: 84 × 2,947 identical cells, 0 differing rows). results.pywritestools/io/results.txt, containing exactly the two unresolved pairs.minimal_set.pyprintsOptimal size found: 14in seconds; enumerating all optima is the slow part. The committedminimal_set_raw.txtused--solver gurobi. The optimum size (14) is solver-independent; which 14 quandles is not.minimal_set.pyoverwritestools/io/minimal_set.txt. Back it up before running if you want to diff.compare_minimal_set.pychecks whether each witness pair is also distinguished by quandles other than its own and its aliases. That strict criterion reports few "distinguished" pairs; the useful output isRefinement Summary(Removable quandles: 5,Final set size: 14 -> 9).- Counting one knot against a few quandles is instant; use
--limit-knots/--limit-quandlesfor a quick sanity check.
Solvers (logic/solver.py). Coloring counts are computed by one of several methods, which the app can cross-check against each other:
| Method | Approach |
|---|---|
| Enumeration | Search every arc coloring |
| DFS | Depth-first search with quandle constraints, connectivity-based variable ordering, inverse-table lookahead, symmetry breaking |
| Linear | Gaussian elimination over 𝔽ₚ for Alexander quandles of prime modulus |
| Propagation | Constraint propagation |
| SNF | Smith normal form, for count-only computations over ℤₙ |
Cocycles (logic/cocycle.py). 2-cocycles are found by solving the linear cocycle condition, then quotienting out coboundaries to get H²; the state-sum invariant is Φ_φ(K) = Σ t^{W(C)} over all colorings.
Quandle selection (tools/minimal_set.py). Each quandle induces a signature on the census. Choosing a small family that separates the most pairs is cast as minimum set cover and solved by integer programming (OR-Tools or Gurobi).
Identification (logic/identifier.py). logic/signature_db.py stores coloring counts for 16 quandles and matches an input diagram by signature. The 16 are the 14-quandle set plus Dihedral(11) and Alexander(Z_11, t=8), added so prime knots do not collide with connected sums in drawing mode. This does not change the 14/9 results above.
app.py Flask entry point and API routes
logic/
geometry.py Stroke geometry and intersection detection
graph.py Crossing graph, arc traversal, over/under state
recognition.py Stroke data to knot diagram
solver.py Quandle coloring solvers (enum/DFS/linear/SNF)
cocycle.py 2-cocycles and cocycle invariants
identifier.py Coloring-signature knot identification
turbo.py Notebook and GAP export
signature_db.py Generated identification database
static/ Front-end (canvas drawing, UI, quandle tables)
templates/index.html Single-page UI
tools/
knotinfo.py KnotInfo CSV/PD notation to knot JSON
turbo_cli.py Batch coloring counts -> CSV
results.py Group knots with identical coloring counts
compare.py Distinguish a pair by colorings and cocycles
minimal_set.py Minimum distinguishing quandle set (set cover)
compare_minimal_set.py Recheck witness pairs, refine 14 -> 9
analyze_minimal_set.py Summarize set-cover output
data_results.py Copy grouped knot JSON files
build_signature_db.py Build logic/signature_db.py
io/ Committed evidence: source CSV and result files
data/ 84 prime-knot JSON files
If you use this code, please cite it as:
@misc{li2026knotstudio,
title = {Knot Studio: quandle coloring and cocycle invariants for
distinguishing prime knots with fewer than 10 crossings},
author = {Li, Xiang},
year = {2026},
howpublished = {\url{https://github.com/mikessslx/knot-studio}},
note = {Supervised by Prof. Marco Farinati, Guangdong Technion - Israel Institute of Technology}
}The numbers in this repository come from a project report of the same title, which is not distributed here.
Please also cite KnotInfo for the knot data.
Code: Apache 2.0. Knot data derives from KnotInfo; see Data.
Supervised by Prof. Marco Farinati (GTIIT). Knot data from KnotInfo (Livingston & Moore). Set cover solved with OR-Tools and Gurobi.