Skip to content

Fix edge-case in ConformanceTests.generate_element(::MPolyRing) #2077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 23, 2025

Conversation

HechtiDerLachs
Copy link
Contributor

It turns out that for the recursive ring interface tests one actually needs to implement divides. We should mention that in the documentation of the ring interface.

@@ -1467,7 +1467,7 @@ function ConformanceTests.generate_element(Rx::MPolyRing)
len_bound = 8
exp_bound = rand(1:5)
len = rand(0:len_bound)
coeffs = [ConformanceTests.generate_element(R) for _ in 1:len]
coeffs = elem_type(R)[ConformanceTests.generate_element(R) for _ in 1:len]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of empty lists, this was of type Vector{Any} despite the generation function being type stable. This caused more problems down the road.

Copy link

codecov bot commented May 14, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.37%. Comparing base (7fcf80b) to head (f520650).
Report is 19 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2077      +/-   ##
==========================================
- Coverage   88.39%   88.37%   -0.02%     
==========================================
  Files         125      126       +1     
  Lines       31578    31639      +61     
==========================================
+ Hits        27914    27962      +48     
- Misses       3664     3677      +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@HechtiDerLachs
Copy link
Contributor Author

Any volunteers for approval? @thofma ? @lgoettgens ?

@thofma
Copy link
Member

thofma commented May 14, 2025

Hm, maybe @fingolfin and @lgoettgens can comment? They worked on the interface tests and probably know if this was intended.

@@ -505,6 +505,8 @@ throwing an exception, returning meaningless results, hanging or crashing. The
function should only be called with `check=false` if it is already known that the
division will be exact.

***Note:*** For the *recursive* ring tests to work, one needs to also implement `divides(f::MyElem, g::MyElem)`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think divides is only required for euclidean rings: only test_EuclideanRing_interface calls it. Which is in turn only invoked automatically for univariate polynomial rings over a field...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And there are generic divides methods for that, i.e.:

divides(f::PolyRingElem{T}, g::PolyRingElem{T}) where T <: RingElement
divides(z::PolyRingElem{T}, x::T) where T <: RingElement

So it would be really good to know what the actual problem you run into was?

@fingolfin
Copy link
Member

This was the backtrace leading to the problem (taking from Slack):

Basic functionality for commutative rings only: Error During Test at /home/matthias/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:232
  Got exception outside of a @test
  function divrem is not implemented for arguments
  MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}: -1
  MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}: -1
  
  Stacktrace:
    [1] divrem(a::MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}, b::MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}})
      @ AbstractAlgebra ~/.julia/packages/AbstractAlgebra/EAjNg/src/algorithms/GenericFunctions.jl:50
    [2] divrem(a::MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}, b::MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}})
      @ AbstractAlgebra ~/.julia/packages/AbstractAlgebra/EAjNg/src/AbstractAlgebra.jl:65
    [3] divides
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/src/algorithms/GenericFunctions.jl:156 [inlined]
    [4] divides_monagan_pearce(a::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}}, b::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}}, bits::Int64)
      @ AbstractAlgebra.Generic ~/.julia/packages/AbstractAlgebra/EAjNg/src/generic/MPoly.jl:2496
    [5] divides(a::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}}, b::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}})
      @ AbstractAlgebra.Generic ~/.julia/packages/AbstractAlgebra/EAjNg/src/generic/MPoly.jl:2555
    [6] #divexact#125
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/src/generic/MPoly.jl:2566 [inlined]
    [7] divexact
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/src/generic/MPoly.jl:2565 [inlined]
    [8] inv(x::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}})
      @ AbstractAlgebra ~/.julia/packages/AbstractAlgebra/EAjNg/src/Rings.jl:53
    [9] inv
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/src/AbstractAlgebra.jl:73 [inlined]
   [10] inv!
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/src/fundamental_interface.jl:337 [inlined]
   [11] test_mutating_op_like_neg(f::typeof(AbstractAlgebra.inv), f!::typeof(inv!), A::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}})
      @ TestExt ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Mutating-ops.jl:14
   [12] macro expansion
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:234 [inlined]
   [13] macro expansion
      @ ~/julia-1.11.1/share/julia/stdlib/v1.11/Test/src/Test.jl:1700 [inlined]
   [14] macro expansion
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:233 [inlined]
   [15] macro expansion
      @ ~/julia-1.11.1/share/julia/stdlib/v1.11/Test/src/Test.jl:1700 [inlined]
   [16] test_Ring_interface(R::AbstractAlgebra.Generic.MPolyRing{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}}; reps::Int64)
      @ TestExt ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:228
   [17] test_Ring_interface
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:222 [inlined]
   [18] macro expansion
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:521 [inlined]
   [19] macro expansion
      @ ~/julia-1.11.1/share/julia/stdlib/v1.11/Test/src/Test.jl:1700 [inlined]
   [20] test_MPoly_interface(Rxy::AbstractAlgebra.Generic.MPolyRing{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}}; reps::Int64)
      @ TestExt ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:521
   [21] test_MPoly_interface
      @ ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:512 [inlined]
   [22] test_Ring_interface_recursive(R::MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}; reps::Int64)
      @ TestExt ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:803
   [23] test_Ring_interface_recursive(R::MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}})
      @ TestExt ~/.julia/packages/AbstractAlgebra/EAjNg/ext/TestExt/Rings-conformance-tests.jl:798
   [24] top-level scope
      @ REPL[10]:1
   [25] eval
      @ ./boot.jl:430 [inlined]
   [26] eval_user_input(ast::Any, backend::REPL.REPLBackend, mod::Module)
      @ REPL ~/julia-1.11.1/share/julia/stdlib/v1.11/REPL/src/REPL.jl:245
   [27] repl_backend_loop(backend::REPL.REPLBackend, get_module::Function)
      @ REPL ~/julia-1.11.1/share/julia/stdlib/v1.11/REPL/src/REPL.jl:342
   [28] start_repl_backend(backend::REPL.REPLBackend, consumer::Any; get_module::Function)
      @ REPL ~/julia-1.11.1/share/julia/stdlib/v1.11/REPL/src/REPL.jl:327
   [29] run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool, backend::Any)
      @ REPL ~/julia-1.11.1/share/julia/stdlib/v1.11/REPL/src/REPL.jl:483
   [30] run_repl(repl::REPL.AbstractREPL, consumer::Any)
      @ REPL ~/julia-1.11.1/share/julia/stdlib/v1.11/REPL/src/REPL.jl:469
   [31] (::Base.var"#1139#1141"{Bool, Symbol, Bool})(REPL::Module)
      @ Base ./client.jl:446
   [32] #invokelatest#2
      @ ./essentials.jl:1055 [inlined]
   [33] invokelatest
      @ ./essentials.jl:1052 [inlined]
   [34] run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool, color_set::Bool)
      @ Base ./client.jl:430
   [35] repl_main
      @ ./client.jl:567 [inlined]
   [36] _start()
      @ Base ./client.jl:541

@fingolfin
Copy link
Member

So this started with test_Ring_interface_recursive(R::MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}})

As a recursive test was requested it then went to test mpoly rings over the given monoid algebra, i.e.:
test_MPoly_interface(Rxy::AbstractAlgebra.Generic.MPolyRing{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}}; reps::Int64)

which then calls test_Ring_interface on that mpoly ring, which ends up doing test_mutating_op_like_neg(f::typeof(AbstractAlgebra.inv), f!::typeof(inv!), A::AbstractAlgebra.Generic.MPoly{MonoidAlgebraElem{QQFieldElem, MonoidAlgebra{QQFieldElem, MPolyQuoRing{MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}}}}})

And that test then ends up doing:

         @test isone(AbstractAlgebra.inv(one(R)))
         test_mutating_op_like_neg(AbstractAlgebra.inv, inv!, one(R))
         test_mutating_op_like_neg(AbstractAlgebra.inv, inv!, -one(R))

This eventually triggers a call to divides(a::MPoly{T}, b::MPoly{T}) where {T <: RingElement}

and so on.

So how to handle this. Some option:

  1. teach the "generic" inv and/or divides methods for polynomial rings to better handle division by a constant polynomial, and there handle division by 1 or -1 (avoiding a call to inv and/or divides in the coefficient ring)
  2. we just don't do this kind of test in the conformance test (not so nice as this has found bugs in the past)
  3. we come up with a way to "gate" this test so that only rings that are declared as "supports division" are tested this way -> this is basically the same problem which stalled PR Add conformance tests for is_unit & is_nilpotent #1947

For 3 one possible way forward would be via the _implements trait from #1957 ...

@fingolfin
Copy link
Member

All that said, I really don't want divides to be a required part of the ring interface.

@thofma
Copy link
Member

thofma commented May 15, 2025

Not clear what the "ring interface" promises. "If you implement these methods for your ring type, then the recursive tests will pass (modulo bugs in your code)"?

I am happy to try out 3). If this is not an option (because of time reasons), what about hiding the test behind a try ... catch e .... !(e isa NotImplementedError)?

@HechtiDerLachs HechtiDerLachs changed the title Update the ring interface docu ~~Update the ring interface docu~~ Fix generation of random polynomials May 23, 2025
@lgoettgens lgoettgens changed the title ~~Update the ring interface docu~~ Fix generation of random polynomials Improve type stability of ConformanceTests.generate_element(::MPolyRing) May 23, 2025
@lgoettgens lgoettgens enabled auto-merge (squash) May 23, 2025 13:22
@HechtiDerLachs
Copy link
Contributor Author

I updated this in view of #2096 . Now it does not contain changes to the docu anymore.

@lgoettgens lgoettgens changed the title Improve type stability of ConformanceTests.generate_element(::MPolyRing) Fix edge-case in ConformanceTests.generate_element(::MPolyRing) May 23, 2025
@lgoettgens lgoettgens merged commit ba700e4 into Nemocas:master May 23, 2025
29 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants