Skip to content
Merged
60 changes: 48 additions & 12 deletions src/MPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -488,51 +488,87 @@ function is_monomial(x::MPolyRingElem{T}) where T <: RingElement
return length(x) == 1 && isone(first(coefficients(x)))
end

function exponent_vector!(e::Vector{S}, a::MPolyRingElem{T}, i::Int) where {T <: RingElement, S}
return S.(exponent_vector(a, i))
end

function coeff!(c::T, a::MPolyRingElem{T}, i::Int) where {T <: RingElement}
return coeff(a, i)
end

function term!(t::T, a::T, i::Int) where {T <: MPolyRingElem}
return term(a, i)
end

function monomial!(m::T, a::T, i::Int) where {T <: MPolyRingElem}
return monomial(a, i)
end

###############################################################################
#
# Iterators
#
###############################################################################

@doc raw"""
coefficients(a::MPolyRingElem{T}) where T <: RingElement
coefficients(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement

Return an iterator for the coefficients of the given polynomial. To retrieve
an array of the coefficients, use `collect(coefficients(a))`.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.
Comment on lines +518 to +521
Copy link
Collaborator Author

@joschmitt joschmitt Nov 10, 2025

Choose a reason for hiding this comment

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

I added these sentences to the doc strings. I'm happy for any suggestions. If I understand the suggestion in oscar-system/Oscar.jl#5530 (comment) correctly, we want to have something to add to all iterator doc strings that allow inplace. So maybe we should agree on a nice wording here and then we can copy and paste it everywhere :)

@fingolfin @mjrodgers

EDIT: I kept this intentionally vague ("may") because with this generic code it might be that the inplace version actually doesn't do anything different (because something is not implemented for a particular type).

Choose a reason for hiding this comment

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

I like this, if others agree I will add this same wording to the other methods in Oscar.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe mention that it might to lead to unexpected behavior when calling collect on such an iterator.

Copy link
Member

Choose a reason for hiding this comment

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

I concur

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a bit more, so this is ready for the next round of feedback :)

I feel like in an ideal world, we would put this paragraph somewhere "central" in the documentation and just link there from all iterators that support inplace = true. But I don't know where.

Copy link
Member

Choose a reason for hiding this comment

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

Looks good to me, thanks. (And I agree, one "central place" would be great, but for the time being this here is fine.)

"""
function coefficients(a::MPolyRingElem{T}) where T <: RingElement
return Generic.MPolyCoeffs(a)
function coefficients(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement
return Generic.MPolyCoeffs(a, inplace=inplace)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return Generic.MPolyCoeffs(a, inplace=inplace)
return Generic.MPolyCoeffs(a; inplace)

IMO a bit neater, but I don't really care

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I never really got the hang of this syntax, so if you don't care, I'll prefer my version.

end

@doc raw"""
exponent_vectors(a::MPolyRingElem{T}) where T <: RingElement
exponent_vectors(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement

Return an iterator for the exponent vectors of the given polynomial. To
retrieve an array of the exponent vectors, use
`collect(exponent_vectors(a))`.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.
"""
function exponent_vectors(a::MPolyRingElem{T}) where T <: RingElement
return Generic.MPolyExponentVectors(a)
function exponent_vectors(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement
return Generic.MPolyExponentVectors(a, inplace=inplace)
end

function exponent_vectors(::Type{Vector{S}}, a::MPolyRingElem{T}; inplace::Bool = false) where {T <: RingElement, S}
return Generic.MPolyExponentVectors(Vector{S}, a, inplace=inplace)
end

@doc raw"""
monomials(a::MPolyRingElem{T}) where T <: RingElement
monomials(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement

Return an iterator for the monomials of the given polynomial. To retrieve
an array of the monomials, use `collect(monomials(a))`.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.
"""
function monomials(a::MPolyRingElem{T}) where T <: RingElement
return Generic.MPolyMonomials(a)
function monomials(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement
return Generic.MPolyMonomials(a, inplace=inplace)
end

@doc raw"""
terms(a::MPolyRingElem{T}) where T <: RingElement
terms(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement

Return an iterator for the terms of the given polynomial. To retrieve
an array of the terms, use `collect(terms(a))`.

If `inplace` is `true`, the elements of the iterator may share their memory. This
means that an element returned by the iterator may be overwritten 'in place' in
the next iteration step. This may result in significantly fewer memory allocations.
"""
function terms(a::MPolyRingElem{T}) where T <: RingElement
return Generic.MPolyTerms(a)
function terms(a::MPolyRingElem{T}; inplace::Bool = false) where T <: RingElement
return Generic.MPolyTerms(a, inplace=inplace)
end

###############################################################################
Expand Down
3 changes: 3 additions & 0 deletions src/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export check_composable
export check_parent
export codomain
export coeff
export coeff!
export coefficient_ring
export coefficient_ring_type
export coefficients
Expand Down Expand Up @@ -211,6 +212,7 @@ export evaluate
export exp_gcd
export exponent
export exponent_vector
export exponent_vector!
export exponent_vectors
export exponent_word
export exponent_words
Expand Down Expand Up @@ -579,6 +581,7 @@ export symbols
export tail
export term
export terms
export term!
export to_univariate
export total_degree
export total_ring_of_fractions
Expand Down
63 changes: 55 additions & 8 deletions src/generic/GenericTypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -385,20 +385,67 @@ end

# Iterators

struct MPolyCoeffs{T <: AbstractAlgebra.NCRingElem}
poly::T
mutable struct MPolyCoeffs{T <: AbstractAlgebra.NCRingElem, S <: AbstractAlgebra.RingElement}
poly::T
inplace::Bool
temp::S # only used if inplace == true

function MPolyCoeffs(f::AbstractAlgebra.NCRingElem; inplace::Bool = false)
I = new{typeof(f), elem_type(coefficient_ring_type(f))}(f, inplace)
if inplace
I.temp = zero(coefficient_ring(parent(f)))
end
return I
end
end

struct MPolyExponentVectors{T <: AbstractAlgebra.RingElem}
poly::T
# S may be the type of anything that can store an exponent vector, for example
# Vector{Int}, ZZMatrix, ...
mutable struct MPolyExponentVectors{T <: AbstractAlgebra.RingElem, S}
poly::T
inplace::Bool
temp::S # only used if inplace == true

function MPolyExponentVectors(f::AbstractAlgebra.NCRingElem; inplace::Bool = false)
return MPolyExponentVectors(Vector{Int}, f, inplace=inplace)
end

function MPolyExponentVectors(::Type{Vector{S}}, f::AbstractAlgebra.NCRingElem; inplace::Bool = false) where S
I = new{typeof(f), Vector{S}}(f, inplace)
if inplace
# Don't use `zeros`: If S === ZZRingElem, then all the entries would be identical
I.temp = [zero(S) for _ in 1:nvars(parent(f))]
end
return I
end
end

struct MPolyTerms{T <: AbstractAlgebra.NCRingElem}
poly::T
mutable struct MPolyTerms{T <: AbstractAlgebra.NCRingElem}
poly::T
inplace::Bool
temp::T # only used if inplace == true

function MPolyTerms(f::AbstractAlgebra.NCRingElem; inplace::Bool = false)
I = new{typeof(f)}(f, inplace)
if inplace
I.temp = zero(parent(f))
end
return I
end
end

struct MPolyMonomials{T <: AbstractAlgebra.NCRingElem}
poly::T
mutable struct MPolyMonomials{T <: AbstractAlgebra.NCRingElem}
poly::T
inplace::Bool
temp::T # only used if inplace == true

function MPolyMonomials(f::AbstractAlgebra.NCRingElem; inplace::Bool = false)
I = new{typeof(f)}(f, inplace)
if inplace
I.temp = zero(parent(f))
end
return I
end
end

mutable struct MPolyBuildCtx{T, S}
Expand Down
115 changes: 56 additions & 59 deletions src/generic/MPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,31 @@ are given in the order of the variables for the ring, as supplied when the
ring was created.
"""
function exponent_vector(a::MPoly{T}, i::Int) where T <: RingElement
e = Vector{Int}(undef, nvars(parent(a)))
return exponent_vector!(e, a, i)
end

function exponent_vector!(e::Vector{S}, a::MPoly{T}, i::Int) where {T <: RingElement, S}
@assert length(e) == nvars(parent(a))
A = a.exps
N = size(A, 1)

ord = internal_ordering(parent(a))
if ord == :lex
return [Int(A[j, i]) for j in N:-1:1]
range = N:-1:1
elseif ord == :deglex
return [Int(A[j, i]) for j in N - 1:-1:1]
range = N - 1:-1:1
elseif ord == :degrevlex
return [Int(A[j, i]) for j in 1:N - 1]
range = 1:N - 1
else
error("invalid ordering")
end
k = 1
for j in range
e[k] = S(A[j, i])
k += 1
end
return e
end

@doc raw"""
Expand Down Expand Up @@ -635,6 +647,11 @@ function coeff(x::MPoly, i::Int)
return x.coeffs[i]
end

# Only for compatibility, we can't do anything in place here
function coeff!(c::T, x::MPoly{T}, i::Int) where T <: RingElement
return x.coeffs[i]
end

function trailing_coefficient(p::MPoly{T}) where T <: RingElement
@req !iszero(p) "Zero polynomial does not have a leading monomial"
return coeff(p, length(p))
Expand Down Expand Up @@ -664,7 +681,9 @@ function monomial!(m::MPoly{T}, x::MPoly{T}, i::Int) where T <: RingElement
N = size(x.exps, 1)
fit!(m, 1)
monomial_set!(m.exps, 1, x.exps, i, N)
m.coeffs[1] = one(base_ring(x))
if !isassigned(m.coeffs, 1) || !is_one(m.coeffs[1])
m.coeffs[1] = one(base_ring(x))
end
Comment on lines +684 to +686
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a bit ugly, but it gets the monomials iterator to constant allocations. Otherwise it would allocate a new 1 all the time. Is this too much of a corner case?

Copy link
Member

Choose a reason for hiding this comment

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

Seems fine to me

m.length = 1
return m
end
Expand All @@ -675,11 +694,17 @@ end
Return the $i$-th nonzero term of the polynomial $x$ (as a polynomial).
"""
function term(x::MPoly, i::Int)
R = base_ring(x)
y = zero(parent(x))
return term!(y, x, i)
end

function term!(y::T, x::T, i::Int) where T <: MPoly
N = size(x.exps, 1)
exps = Matrix{UInt}(undef, N, 1)
monomial_set!(exps, 1, x.exps, i, N)
return parent(x)([deepcopy(x.coeffs[i])], exps)
fit!(y, 1)
monomial_set!(y.exps, 1, x.exps, i, N)
y.coeffs[1] = deepcopy(x.coeffs[i])
y.length = 1
return y
end

@doc raw"""
Expand Down Expand Up @@ -804,69 +829,41 @@ Base.copy(f::Generic.MPoly) = deepcopy(f)
#
###############################################################################

function Base.iterate(x::MPolyCoeffs)
if length(x.poly) >= 1
return coeff(x.poly, 1), 1
else
return nothing
end
end

function Base.iterate(x::MPolyCoeffs, state)
state += 1
if length(x.poly) >= state
return coeff(x.poly, state), state
else
return nothing
end
end

function Base.iterate(x::MPolyExponentVectors)
if length(x.poly) >= 1
return exponent_vector(x.poly, 1), 1
else
return nothing
end
end

function Base.iterate(x::MPolyExponentVectors, state)
state += 1
if length(x.poly) >= state
return exponent_vector(x.poly, state), state
else
return nothing
end
end

function Base.iterate(x::MPolyTerms)
if length(x.poly) >= 1
return term(x.poly, 1), 1
function Base.iterate(x::MPolyCoeffs, state::Union{Nothing, Int} = nothing)
s = isnothing(state) ? 1 : state + 1
if length(x.poly) >= s
c = x.inplace ? coeff!(x.temp, x.poly, s) : coeff(x.poly, s)
return c, s
else
return nothing
end
end

function Base.iterate(x::MPolyTerms, state)
state += 1
if length(x.poly) >= state
return term(x.poly, state), state
function Base.iterate(x::MPolyExponentVectors, state::Union{Nothing, Int} = nothing)
s = isnothing(state) ? 1 : state + 1
if length(x.poly) >= s
v = x.inplace ? exponent_vector!(x.temp, x.poly, s) : exponent_vector(x.poly, s)
return v, s
else
return nothing
end
end

function Base.iterate(x::MPolyMonomials)
if length(x.poly) >= 1
return monomial(x.poly, 1), 1
function Base.iterate(x::MPolyTerms, state::Union{Nothing, Int} = nothing)
s = isnothing(state) ? 1 : state + 1
if length(x.poly) >= s
t = x.inplace ? term!(x.temp, x.poly, s) : term(x.poly, s)
return t, s
else
return nothing
end
end

function Base.iterate(x::MPolyMonomials, state)
state += 1
if length(x.poly) >= state
return monomial(x.poly, state), state
function Base.iterate(x::MPolyMonomials, state::Union{Nothing, Int} = nothing)
s = isnothing(state) ? 1 : state + 1
if length(x.poly) >= s
m = x.inplace ? monomial!(x.temp, x.poly, s) : monomial(x.poly, s)
return m, s
else
return nothing
end
Expand All @@ -876,12 +873,12 @@ function Base.length(x::Union{MPolyCoeffs, MPolyExponentVectors, MPolyTerms, MPo
return length(x.poly)
end

function Base.eltype(::Type{MPolyCoeffs{T}}) where T <: AbstractAlgebra.MPolyRingElem{S} where S <: RingElement
function Base.eltype(::Type{MPolyCoeffs{T, S}}) where {T <: AbstractAlgebra.MPolyRingElem, S <: RingElement}
return S
end

function Base.eltype(::Type{MPolyExponentVectors{T}}) where T <: AbstractAlgebra.MPolyRingElem{S} where S <: RingElement
return Vector{Int}
function Base.eltype(::Type{MPolyExponentVectors{T, V}}) where {V, T <: AbstractAlgebra.MPolyRingElem{S} where S <: RingElement}
return V
end

function Base.eltype(::Type{MPolyMonomials{T}}) where T <: AbstractAlgebra.MPolyRingElem{S} where S <: RingElement
Expand Down
Loading
Loading