Skip to content

Commit 9113da7

Browse files
fix: post-Bareiss follow-up — conway marker removed, polynomial bounds 12 → 128 (unblocks red main) (#85)
## Summary **`main` is currently red and this unblocks it.** KnotTheory.jl#48 merged, so quandledb's `@test_broken` on `conway(figure_eight)` now reports **"Unexpected Pass"** — a hard error, and precisely the forcing signal the marker existed to produce. Reproduced locally against KnotTheory `a2f2938`. ### 1. Conway marker → hard assertion Upstream now normalises Alexander to **Δ(1) = +1** (the Conway normalisation) instead of "leading coefficient positive", which was the wrong canonical choice on mixed-sign knots. `conway(figure_eight)` = `1 − z²` exactly, so the assertion is restored. ### 2. Alexander/Conway bounds: 12 → 128 The bound of 12 in #82 was calibrated against the **O(n!)** cofactor determinant that #48 replaced with **O(n³)** Bareiss elimination. Re-measured on `s1^n` torus closures (the worst case — no arc merging): | crossings | alexander, Bareiss | alexander, cofactor (before) | |---|---|---| | 16 | ~0 s | **> 6 h** (the CI kill) | | 40 | 0.07 s | — | | 100 | 0.10 s | — | | 140 | 0.61 s | — | 128 keeps the worst case sub-second while covering every realistic diagram. **Jones (20) and HOMFLY (15) are unchanged** — those are genuinely exponential (Kauffman bracket 2ⁿ; skein recursion), and their bounds come from upstream's own limits. ### 3. Guard testset reworked With the three bounds now an order of magnitude apart, one diagram can no longer exercise them all — the old single-fixture test would have silently stopped testing the alexander/conway guard. It now covers: - **16 crossings** — only HOMFLY defers; alexander, conway and jones must all be *real* values. - **130 crossings** — all four defer, with an explicit `@test length(pd.crossings) > ALEXANDER_MAX_CROSSINGS` so that raising the bound without raising the fixture fails loudly instead of going quiet. ## Verification Full axioms suite against KnotTheory `a2f2938`: passes with **zero broken markers** — a first for this suite. ## Note on the BR-5 fuzz residual Re-measured at 200 trials: **still exactly 11/200**, unchanged by #48. My expectation that the sign fix would shrink it was **wrong** — the mismatches are not caused by the alexander/conway sign convention. A component-level diagnostic is running to identify the actual cause; the `@test_broken` counter marker stays until that is known, and the finding will be posted to KnotTheory#42 (and appended here if it changes anything in this PR). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 8a61c94 commit 9113da7

4 files changed

Lines changed: 83 additions & 48 deletions

File tree

.github/workflows/krl-verification.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ jobs:
6868
julia --color=yes server/krl/test/seam_test.jl
6969
7070
# server/Project.toml [sources] resolves KnotTheory/Skein/AcceleratorGate
71-
# at server/../../../<name>.jl — i.e. two directories above the repo
72-
# checkout ($GITHUB_WORKSPACE/../../<name>.jl). actions/checkout cannot
73-
# write outside the workspace, so check out under deps/ and symlink.
71+
# at server/../../<name>.jl — i.e. one directory above the repo checkout
72+
# ($GITHUB_WORKSPACE/../<name>.jl). actions/checkout cannot write outside
73+
# the workspace, so check out under deps/ and symlink.
74+
# NOTE: keep this in step with server/Project.toml — #83 moved [sources]
75+
# from ../../../ to ../../ without updating the symlink here, which broke
76+
# every instantiate with "expected package KnotTheory to exist at path".
7477
- name: Checkout KnotTheory.jl
7578
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
7679
with:
@@ -93,8 +96,19 @@ jobs:
9396
run: |
9497
set -euo pipefail
9598
for p in KnotTheory.jl Skein.jl AcceleratorGate.jl; do
96-
ln -sfn "$GITHUB_WORKSPACE/deps/$p" "$GITHUB_WORKSPACE/../../$p"
99+
ln -sfn "$GITHUB_WORKSPACE/deps/$p" "$GITHUB_WORKSPACE/../$p"
97100
done
101+
# Fail loudly here rather than inside Pkg.instantiate, whose error
102+
# ("expected package ... to exist at path") names the path but not
103+
# the cause.
104+
julia --project=server -e '
105+
using TOML
106+
src = TOML.parsefile("server/Project.toml")["sources"]
107+
for (name, spec) in src
108+
path = normpath(joinpath("server", spec["path"]))
109+
isdir(path) || error("[sources] $name -> $(spec["path"]) resolves to $path, which does not exist; the symlink step above is out of step with server/Project.toml")
110+
end
111+
println("PASS: all [sources] paths resolve")'
98112
99113
- name: Instantiate server project
100114
run: julia --color=yes --project=server -e 'using Pkg; Pkg.instantiate()'

server/quandle_semantic.jl

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -469,15 +469,19 @@ end
469469
const HOMFLY_MAX_CROSSINGS = 15
470470
const HOMFLY_DEFERRED_SENTINEL = "deferred:too_many_crossings"
471471

472-
# KnotTheory's alexander_polynomial computes its polynomial-matrix minor
473-
# determinant by naive cofactor expansion — O(n!) in the minor size, which
474-
# tracks the crossing count. Measured on (2,n)-torus closures (s1^n, the
475-
# worst case: no arc merging): n=12 → 12s, n=13 → 106s, n=16 → 6h+ (this
476-
# hung CI to GitHub's job kill). conway_polynomial CALLS alexander_polynomial
477-
# internally, so it inherits the same bound. jones_polynomial goes through
478-
# the Kauffman bracket instead, which KnotTheory itself hard-limits at 20
479-
# crossings by THROWING — we convert that throw into the same sentinel.
480-
const ALEXANDER_MAX_CROSSINGS = 12
472+
# alexander_polynomial's determinant is now fraction-free Bareiss
473+
# elimination — O(n^3) polynomial operations (KnotTheory.jl#46/#48).
474+
# Re-measured on (2,n)-torus closures (s1^n, the worst case: no arc
475+
# merging): n=40 → 0.07s, n=100 → 0.10s, n=140 → 0.61s. The previous
476+
# bound of 12 was calibrated against the O(n!) cofactor expansion it
477+
# replaced (n=12 → 12s, n=13 → 106s, n=16 → 6h+ and a CI job killed at
478+
# GitHub's 6-hour limit), so it is now an order of magnitude too tight.
479+
# 128 keeps the worst case sub-second while covering every realistic
480+
# diagram. conway_polynomial calls alexander_polynomial, so it shares
481+
# the bound. jones_polynomial instead goes through the Kauffman bracket,
482+
# which is genuinely 2^n and which KnotTheory hard-limits at 20 crossings
483+
# by THROWING — we convert that throw into the same sentinel.
484+
const ALEXANDER_MAX_CROSSINGS = 128
481485
const CONWAY_MAX_CROSSINGS = ALEXANDER_MAX_CROSSINGS
482486
const JONES_MAX_CROSSINGS = 20
483487

@@ -498,7 +502,7 @@ end
498502
_safe_alexander_serialised(pd::KnotTheory.PlanarDiagram) -> String
499503
500504
Alexander polynomial, guarded by `ALEXANDER_MAX_CROSSINGS` (the upstream
501-
determinant is O(n!) cofactor expansion). Returns the deferred sentinel
505+
determinant is O(n^3) Bareiss elimination). Returns the deferred sentinel
502506
above the bound.
503507
"""
504508
function _safe_alexander_serialised(pd::KnotTheory.PlanarDiagram)::String
@@ -510,7 +514,7 @@ end
510514
_safe_conway_serialised(pd::KnotTheory.PlanarDiagram) -> String
511515
512516
Conway polynomial, guarded by `CONWAY_MAX_CROSSINGS` (upstream it is
513-
computed FROM the Alexander polynomial, so it shares the O(n!) cost).
517+
computed FROM the Alexander polynomial, so it shares that cost).
514518
Returns the deferred sentinel above the bound.
515519
"""
516520
function _safe_conway_serialised(pd::KnotTheory.PlanarDiagram)::String

server/test_br5_fuzz.jl

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,23 @@ end
120120

121121
@testset "BR-5: crossing-order invariance on random braid words" begin
122122
rng = MersenneTwister(BR5_SEED + 1)
123-
# KNOWN-BROKEN (upstream): the polynomial segments of quandle_key
124-
# (alexander/conway/homfly from KnotTheory.jl) are crossing-order
125-
# sensitive on some diagrams — the polynomials are not normalised to a
126-
# canonical unit (±t^k), so 25/200 seeded trials disagree. The layers
127-
# this repo owns (presentation_hash, colouring counts) are asserted
128-
# hard below; the key mismatch count is tracked and marked broken so
129-
# the marker must be removed once KnotTheory.jl normalises.
123+
# KNOWN-BROKEN (upstream KnotTheory.jl#51): the polynomial segments of
124+
# quandle_key are crossing-order sensitive on multi-component LINKS.
125+
# Measured over this 200-trial corpus, splitting by Delta(1):
126+
# links (Delta(1)=0): 11 mismatch, 87 agree
127+
# knots (Delta(1)=±1): 0 mismatch, 102 agree
128+
# Knots became fully order-invariant when KnotTheory#48 landed the
129+
# Delta(1)=+1 normalisation; that fix cannot apply to links, where
130+
# Delta(1)=0 and the code falls back to an order-sensitive rule. Worse,
131+
# conway_polynomial reconstructs from an assumed symmetry about
132+
# exponent 0, which no link satisfies (even span ⟹ half-integer
133+
# centre), so it returns genuinely DIFFERENT polynomials for the same
134+
# link (e.g. 1 + z² vs 1) — not merely a different unit.
135+
# NOTE: this count did NOT move when #48 merged. The earlier
136+
# attribution of these mismatches to the alexander/conway sign
137+
# convention was wrong; the sign fix was real but orthogonal.
138+
# The layers this repo owns (presentation_hash, colouring counts) are
139+
# asserted hard below and hold on every trial.
130140
quandle_key_mismatches = 0
131141
for trial in 1:BR5_TRIALS
132142
nstrands = rand(rng, 3:4)

server/test_quandle_axioms.jl

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -512,22 +512,32 @@ end
512512
end
513513

514514
@testset "KT-2 ext: polynomial guards return sentinels above their bounds" begin
515-
# Build a synthetic 16-crossing PD by braid word s1^16 — a genuine
516-
# (2,16)-torus-link diagram (post KnotTheory#43; before that fix the
517-
# broken braid closure made this degenerate and cheap, which is how the
518-
# missing guards went unnoticed). 16 crossings exceeds
519-
# HOMFLY_MAX_CROSSINGS (15) and ALEXANDER/CONWAY_MAX_CROSSINGS (12,
520-
# the O(n!) upstream determinant: 12 → 12s, 13 → 106s, 16 → 6h+ and a
521-
# CI job killed at GitHub's 6-hour limit), but not JONES_MAX_CROSSINGS
522-
# (20, Kauffman bracket: 2.8s measured at 16).
523-
pd_large = from_braid_word(join(["s1" for _ in 1:16], ".")).pd
524-
d_large = quandle_descriptor(pd_large)
525-
@test d_large.homfly_polynomial == HOMFLY_DEFERRED_SENTINEL
526-
@test d_large.alexander_polynomial == HOMFLY_DEFERRED_SENTINEL
527-
@test d_large.conway_polynomial == HOMFLY_DEFERRED_SENTINEL
528-
# Jones stays real at 16 crossings — a sentinel here would mean the
529-
# bound was tightened without re-measuring.
530-
@test d_large.jones_polynomial != HOMFLY_DEFERRED_SENTINEL
515+
# Each polynomial has its OWN bound, set by its own measured cost model,
516+
# so one diagram cannot exercise them all. Both cases below are genuine
517+
# (2,n)-torus-link diagrams (post KnotTheory#43 — before that fix the
518+
# broken braid closure made them degenerate and cheap, which is how the
519+
# missing guards went unnoticed until CI hung for 6 hours).
520+
521+
# 16 crossings: above HOMFLY's 15 (skein recursion is exponential), but
522+
# below Jones' 20 (Kauffman bracket, 2.8s measured here) and far below
523+
# alexander/conway's 128 (Bareiss, O(n^3)).
524+
pd16 = from_braid_word(join(["s1" for _ in 1:16], ".")).pd
525+
d16 = quandle_descriptor(pd16)
526+
@test d16.homfly_polynomial == HOMFLY_DEFERRED_SENTINEL
527+
@test d16.jones_polynomial != HOMFLY_DEFERRED_SENTINEL
528+
@test d16.alexander_polynomial != HOMFLY_DEFERRED_SENTINEL
529+
@test d16.conway_polynomial != HOMFLY_DEFERRED_SENTINEL
530+
531+
# 130 crossings: above every bound — all four defer. This also pins the
532+
# alexander/conway bound itself: if someone raises it without raising
533+
# this fixture, the test fails rather than silently going quiet.
534+
pd_over = from_braid_word(join(["s1" for _ in 1:130], ".")).pd
535+
@test length(pd_over.crossings) > ALEXANDER_MAX_CROSSINGS
536+
d_over = quandle_descriptor(pd_over)
537+
@test d_over.alexander_polynomial == HOMFLY_DEFERRED_SENTINEL
538+
@test d_over.conway_polynomial == HOMFLY_DEFERRED_SENTINEL
539+
@test d_over.jones_polynomial == HOMFLY_DEFERRED_SENTINEL
540+
@test d_over.homfly_polynomial == HOMFLY_DEFERRED_SENTINEL
531541
end
532542

533543
@testset "KT-2 ext: Jones polynomial distinguishes the test knots" begin
@@ -675,17 +685,14 @@ end
675685
t = quandle_descriptor(trefoil().pd)
676686
@test t.conway_polynomial == "0:1,2:1"
677687

678-
# Figure-eight ∇(z) = 1 - z² → "0:1,2:-1"
679-
# KNOWN-BROKEN (upstream KnotTheory.jl#42): the computed value is
680-
# "0:-1,2:1" = -(1 - z²). Conway is canonically normalised (∇(unknot)=1,
681-
# so there is NO legitimate unit ambiguity) — the sign flip comes from
682-
# the Alexander determinant's row/sign convention on mixed-sign
683-
# diagrams, the same missing unit normalisation tracked in #42. The
684-
# trefoil and cinquefoil (all-positive crossings) are unaffected.
685-
# First caught here: this fixture suite (issue #32) merged behind the
686-
# 6-hour CI hang and had never actually executed before.
688+
# Figure-eight ∇(z) = 1 - z² → "0:1,2:-1". This was @test_broken: the
689+
# computed value was -(1 - z²) because alexander normalised its unit by
690+
# "leading coefficient positive", the wrong canonical choice on
691+
# mixed-sign knots. Fixed upstream (KnotTheory.jl#48) by normalising to
692+
# Δ(1) = +1, the Conway normalisation — the marker then reported
693+
# "unexpected pass", which is exactly the signal it existed to produce.
687694
f = quandle_descriptor(figure_eight().pd)
688-
@test_broken f.conway_polynomial == "0:1,2:-1"
695+
@test f.conway_polynomial == "0:1,2:-1"
689696

690697
# Cinquefoil ∇(z) = 1 + 3z² + z⁴ → "0:1,2:3,4:1"
691698
c = quandle_descriptor(cinquefoil().pd)

0 commit comments

Comments
 (0)