diff --git a/server/quandle_semantic.jl b/server/quandle_semantic.jl index e5bd51d..daaf6a9 100644 --- a/server/quandle_semantic.jl +++ b/server/quandle_semantic.jl @@ -401,6 +401,46 @@ function _serialise_int_poly(poly::Dict{Int,Int})::String isempty(pairs) ? "0:0" : join(pairs, ",") end +""" + _serialise_int_poly_2var(poly::Dict{Tuple{Int,Int},Int}) -> String + +Serialise a two-variable Laurent polynomial-as-dict (e.g. HOMFLY-PT, +keyed by `(a_exp, z_exp) -> coeff`) to the canonical form +`"a_exp,z_exp:coeff;a_exp,z_exp:coeff;..."` (zero coefficients dropped, +keys sorted lexicographically by `(a_exp, z_exp)`). + +Distinct from `_serialise_int_poly` so single-variable and two-variable +polynomials are unambiguously different strings. +""" +function _serialise_int_poly_2var(poly::Dict{Tuple{Int,Int},Int})::String + pairs = String[] + for key in sort(collect(keys(poly))) + coeff = poly[key] + coeff == 0 && continue + push!(pairs, string(key[1], ",", key[2], ":", coeff)) + end + isempty(pairs) ? "0,0:0" : join(pairs, ";") +end + +# Hard limit from KnotTheory.MAX_CROSSINGS_FOR_HOMFLY = 15. Above this, +# HOMFLY's skein recursion is too expensive to keep `quandle_descriptor` +# cheap; we return a sentinel rather than throwing. +const HOMFLY_MAX_CROSSINGS = 15 +const HOMFLY_DEFERRED_SENTINEL = "deferred:too_many_crossings" + +""" + _safe_homfly_serialised(pd::KnotTheory.PlanarDiagram) -> String + +Compute and serialise the HOMFLY-PT polynomial of `pd`, guarded by the +`HOMFLY_MAX_CROSSINGS` bound. Returns the sentinel string +`HOMFLY_DEFERRED_SENTINEL` if `pd` has more than 15 crossings. +""" +function _safe_homfly_serialised(pd::KnotTheory.PlanarDiagram)::String + length(pd.crossings) > HOMFLY_MAX_CROSSINGS && return HOMFLY_DEFERRED_SENTINEL + poly = KnotTheory.homfly_polynomial(pd) + _serialise_int_poly_2var(poly) +end + """ quandle_descriptor(pd::KnotTheory.PlanarDiagram) -> NamedTuple @@ -429,6 +469,12 @@ function quandle_descriptor(pd::KnotTheory.PlanarDiagram) alexander_poly = KnotTheory.alexander_polynomial(pd) alexander_str = _serialise_int_poly(alexander_poly) + # KT-2 extension: Jones, Conway (both Laurent in one variable), + # and HOMFLY-PT (two-variable, guarded at HOMFLY_MAX_CROSSINGS). + jones_str = _serialise_int_poly(KnotTheory.jones_polynomial(pd)) + conway_str = _serialise_int_poly(KnotTheory.conway_polynomial(pd)) + homfly_str = _safe_homfly_serialised(pd) + key = string( canon.generator_count, ":", rel_count, ":", @@ -437,7 +483,10 @@ function quandle_descriptor(pd::KnotTheory.PlanarDiagram) color5, ":", image_hist_3_str, ":", image_hist_5_str, ":", - alexander_str, + alexander_str, ":", + jones_str, ":", + conway_str, ":", + homfly_str, ) ( @@ -453,6 +502,9 @@ function quandle_descriptor(pd::KnotTheory.PlanarDiagram) image_histogram_3 = image_hist_3, image_histogram_5 = image_hist_5, alexander_polynomial = alexander_str, + jones_polynomial = jones_str, + conway_polynomial = conway_str, + homfly_polynomial = homfly_str, quandle_key = key, ) end diff --git a/server/test_quandle_axioms.jl b/server/test_quandle_axioms.jl index 9ca55f6..19b2c1f 100644 --- a/server/test_quandle_axioms.jl +++ b/server/test_quandle_axioms.jl @@ -347,4 +347,100 @@ end @test f.alexander_polynomial != c.alexander_polynomial end +# --------------------------------------------------------------------------- +# § 12. KT-2 extension — Jones, Conway, HOMFLY populated + non-trivial +# +# Same shape as the Alexander tests in §11. The three polynomial fields +# extend the quandle_descriptor; together with Alexander they form a +# strictly more discriminating descriptor. +# +# Exact-value assertions are deliberately limited to "populated + does +# not equal the trivial-unknot output". A separate exact-fixture suite +# is queued (see KT-2 extension follow-up issue) where the test corpus +# captures the exact strings KnotTheory.jl produces. +# --------------------------------------------------------------------------- + +@testset "KT-2 ext: Jones polynomial is populated + non-trivial" begin + for (pd, label) in [ + (trefoil().pd, "trefoil"), + (figure_eight().pd, "figure-eight"), + (cinquefoil().pd, "cinquefoil"), + ] + @testset label begin + d = quandle_descriptor(pd) + @test !isempty(d.jones_polynomial) + @test d.jones_polynomial != "0:0" + end + end +end + +@testset "KT-2 ext: Conway polynomial is populated + non-trivial" begin + for (pd, label) in [ + (trefoil().pd, "trefoil"), + (figure_eight().pd, "figure-eight"), + (cinquefoil().pd, "cinquefoil"), + ] + @testset label begin + d = quandle_descriptor(pd) + @test !isempty(d.conway_polynomial) + @test d.conway_polynomial != "0:0" + end + end +end + +@testset "KT-2 ext: HOMFLY polynomial is populated within bounds" begin + for (pd, label) in [ + (trefoil().pd, "trefoil"), + (figure_eight().pd, "figure-eight"), + (cinquefoil().pd, "cinquefoil"), + ] + @testset label begin + d = quandle_descriptor(pd) + # All three test knots have < 15 crossings, so HOMFLY computes. + @test d.homfly_polynomial != "deferred:too_many_crossings" + @test !isempty(d.homfly_polynomial) + # HOMFLY is two-variable; format is "a_exp,z_exp:coeff;..." + @test occursin(';', d.homfly_polynomial) || + occursin(',', d.homfly_polynomial) + end + end +end + +@testset "KT-2 ext: HOMFLY guard returns sentinel above MAX_CROSSINGS" begin + # Build a synthetic 16-crossing PD by braid word s1^16. KnotTheory's + # MAX_CROSSINGS_FOR_HOMFLY is 15, so this triggers the guard. + pd_large = from_braid_word(join(["s1" for _ in 1:16], ".")).pd + d_large = quandle_descriptor(pd_large) + @test d_large.homfly_polynomial == "deferred:too_many_crossings" +end + +@testset "KT-2 ext: Jones polynomial distinguishes the test knots" begin + t = quandle_descriptor(trefoil().pd) + f = quandle_descriptor(figure_eight().pd) + c = quandle_descriptor(cinquefoil().pd) + @test t.jones_polynomial != f.jones_polynomial + @test t.jones_polynomial != c.jones_polynomial + @test f.jones_polynomial != c.jones_polynomial +end + +@testset "KT-2 ext: Conway polynomial distinguishes the test knots" begin + t = quandle_descriptor(trefoil().pd) + f = quandle_descriptor(figure_eight().pd) + c = quandle_descriptor(cinquefoil().pd) + @test t.conway_polynomial != f.conway_polynomial + @test t.conway_polynomial != c.conway_polynomial + @test f.conway_polynomial != c.conway_polynomial +end + +@testset "KT-2 ext: HOMFLY distinguishes the test knots" begin + # HOMFLY-PT subsumes both Alexander and Jones; for these knots it + # should distinguish all pairs. + t = quandle_descriptor(trefoil().pd) + f = quandle_descriptor(figure_eight().pd) + c = quandle_descriptor(cinquefoil().pd) + @test t.homfly_polynomial != f.homfly_polynomial + @test t.homfly_polynomial != c.homfly_polynomial + @test f.homfly_polynomial != c.homfly_polynomial +end + println("quandle-axiom-tests-ok")