diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3978a5d..5e1f3c9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,16 @@ jobs: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - uses: julia-actions/cache@v3 + - name: MP + shell: julia --project=@. {0} + run: | + using Pkg + Pkg.add([ + PackageSpec(name="DynamicPolynomials", rev="master"), + PackageSpec(name="TypedPolynomials", rev="master"), + PackageSpec(name="StarAlgebras", rev="bl/term"), + ]) + - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 #with: diff --git a/src/MultivariatePolynomials.jl b/src/MultivariatePolynomials.jl index 8014b37f..b9301c87 100644 --- a/src/MultivariatePolynomials.jl +++ b/src/MultivariatePolynomials.jl @@ -17,19 +17,12 @@ polynomial of only one term. """ abstract type AbstractPolynomialLike{T} <: MA.AbstractMutable end -""" - AbstractTermLike{T} - -Abstract type for a value that can act like a term. For instance, an `AbstractMonomial` is an `AbstractTermLike{Int}` since it can act as a term with coefficient `1`. -""" -abstract type AbstractTermLike{T} <: AbstractPolynomialLike{T} end - """ AbstractMonomialLike Abstract type for a value that can act like a monomial. For instance, an `AbstractVariable` is an `AbstractMonomialLike` since it can act as a monomial of one variable with degree `1`. """ -abstract type AbstractMonomialLike <: AbstractTermLike{Int} end +abstract type AbstractMonomialLike <: AbstractPolynomialLike{Int} end """ AbstractVariable <: AbstractMonomialLike @@ -46,11 +39,20 @@ Abstract type for a monomial, i.e. a product of variables elevated to a nonnegat abstract type AbstractMonomial <: AbstractMonomialLike end """ - AbstractTerm{T} <: AbstractTermLike{T} + AbstractTermLike{T} + +Union type for values that can act like a term. This includes `AbstractMonomialLike` +(which acts as a term with coefficient `1`) and `SA.Term{T}`. +""" +const AbstractTermLike{T} = Union{AbstractMonomialLike,SA.Term{T}} + +""" + AbstractTerm{T} -Abstract type for a term of coefficient type `T`, i.e. the product between a value of type `T` and a monomial. +Type alias for a term of coefficient type `T`, i.e. the product between a +value of type `T` and a monomial. This is [`StarAlgebras.Term{T}`](@ref). """ -abstract type AbstractTerm{T} <: AbstractTermLike{T} end +const AbstractTerm{T} = SA.Term{T} """ AbstractPolynomial{T} <: AbstractPolynomialLike{T} @@ -59,7 +61,7 @@ Abstract type for a polynomial of coefficient type `T`, i.e. a sum of `AbstractT """ abstract type AbstractPolynomial{T} <: AbstractPolynomialLike{T} end -const _APL{T} = AbstractPolynomialLike{T} +const _APL{T} = Union{AbstractPolynomialLike{T},SA.Term{T}} include("zip.jl") include("lazy_iterators.jl") diff --git a/src/comparison.jl b/src/comparison.jl index aa6092f8..8f14472d 100644 --- a/src/comparison.jl +++ b/src/comparison.jl @@ -1,11 +1,13 @@ Base.iszero(v::AbstractVariable) = false Base.iszero(m::AbstractMonomial) = false -Base.iszero(t::AbstractTerm) = iszero(coefficient(t)) +# SA.Term already has `iszero` defined in StarAlgebras Base.iszero(t::AbstractPolynomial) = iszero(nterms(t)) Base.isone(v::AbstractVariable) = false Base.isone(m::AbstractMonomial) = isconstant(m) -Base.isone(t::AbstractTerm) = isone(coefficient(t)) && isconstant(monomial(t)) +# isone for SA.Term is defined in StarAlgebras using `isone(basis_element(t))`. +# For polynomials, `isone(monomial)` means `isconstant(monomial)` which should +# be equivalent to `isone(monomial)` as defined by the monomial type. function Base.isone(p::AbstractPolynomial) return isone(nterms(p)) && isone(first(terms(p))) end @@ -55,33 +57,20 @@ end function Base.:(==)(v::AbstractVariable, mono::AbstractMonomial) return isone(degree(mono)) && v == variable(mono) end -function Base.:(==)(t::AbstractTerm, mono::AbstractMonomialLike) +function Base.:(==)(t::SA.Term, mono::AbstractMonomialLike) return isone(coefficient(t)) && monomial(t) == mono end -function Base.:(==)(mono::AbstractMonomialLike, t::AbstractTerm) +function Base.:(==)(mono::AbstractMonomialLike, t::SA.Term) return isone(coefficient(t)) && mono == monomial(t) end -function _compare_term(t1::AbstractTerm, t2::AbstractTerm, comp) - c1 = coefficient(t1) - c2 = coefficient(t2) - if iszero(c1) - iszero(c2) - else - comp(c1, c2) && comp(monomial(t1), monomial(t2)) - end -end - -Base.:(==)(t1::AbstractTerm, t2::AbstractTerm) = _compare_term(t1, t2, ==) -Base.:(==)(p::AbstractPolynomial, t::AbstractTerm) = right_term_eq(p, t) -Base.:(==)(t::AbstractTerm, p::AbstractPolynomial) = right_term_eq(p, t) -function Base.isequal(t1::AbstractTerm, t2::AbstractTerm) - return _compare_term(t1, t2, isequal) -end -function Base.isequal(p::AbstractPolynomial, t::AbstractTerm) +# ==, isequal for SA.Term × SA.Term are defined in StarAlgebras +Base.:(==)(p::AbstractPolynomial, t::SA.Term) = right_term_eq(p, t) +Base.:(==)(t::SA.Term, p::AbstractPolynomial) = right_term_eq(p, t) +function Base.isequal(p::AbstractPolynomial, t::SA.Term) return right_term_eq(p, t; comp = isequal) end -function Base.isequal(t::AbstractTerm, p::AbstractPolynomial) +function Base.isequal(t::SA.Term, p::AbstractPolynomial) return right_term_eq(p, t; comp = isequal) end diff --git a/src/conversion.jl b/src/conversion.jl index c6969123..89fdaecb 100644 --- a/src/conversion.jl +++ b/src/conversion.jl @@ -1,7 +1,10 @@ function convert_constant end -Base.convert(::Type{P}, α) where {P<:_APL} = convert_constant(P, α) -function convert_constant(::Type{TT}, α) where {T,TT<:AbstractTerm{T}} - return term(convert(T, α), constant_monomial(TT)) +Base.convert(::Type{P}, α) where {P<:AbstractPolynomialLike} = convert_constant(P, α) +function Base.convert(::Type{SA.Term{T,M}}, α) where {T,M} + return convert_constant(SA.Term{T,M}, α) +end +function convert_constant(::Type{SA.Term{T,M}}, α) where {T,M} + return term(convert(T, α), constant_monomial(SA.Term{T,M})) end function convert_constant(::Type{PT}, α) where {PT<:AbstractPolynomial} return convert(PT, convert(term_type(PT), α)) @@ -35,7 +38,7 @@ end function Base.convert( ::Type{M}, - t::AbstractTerm, + t::SA.Term, ) where {M<:AbstractMonomialLike} if isone(coefficient(t)) return convert(M, monomial(t)) @@ -43,32 +46,11 @@ function Base.convert( throw(InexactError(:convert, M, t)) end end -function Base.convert( - TT::Type{<:AbstractTerm{T}}, - m::AbstractMonomialLike, -) where {T} - return convert(TT, term(one(T), convert(monomial_type(TT), m))) -end -function Base.convert(TT::Type{<:AbstractTerm{T}}, t::AbstractTerm) where {T} - return convert( - TT, - term( - convert(T, coefficient(t)), - convert(monomial_type(TT), monomial(t)), - ), - ) -end - -# Base.convert(::Type{T}, t::T) where {T <: AbstractTerm} is ambiguous with above method. -# we need the following: -function Base.convert(::Type{TT}, t::TT) where {T,TT<:AbstractTerm{T}} - return t -end function Base.convert( ::Type{T}, p::AbstractPolynomial, -) where {T<:AbstractTermLike} +) where {T<:AbstractMonomialLike} if iszero(nterms(p)) convert(T, zero_term(p)) elseif isone(nterms(p)) @@ -77,6 +59,19 @@ function Base.convert( throw(InexactError(:convert, T, p)) end end +# Disambiguation: SA.Term{T,M} from AbstractPolynomial (more specific than the α catch-all) +function Base.convert( + ::Type{SA.Term{T,M}}, + p::AbstractPolynomial, +) where {T,M} + if iszero(nterms(p)) + convert(SA.Term{T,M}, zero_term(p)) + elseif isone(nterms(p)) + convert(SA.Term{T,M}, leading_term(p)) + else + throw(InexactError(:convert, SA.Term{T,M}, p)) + end +end MA.scaling(p::AbstractPolynomialLike{T}) where {T} = convert(T, p) # Conversion polynomial -> constant @@ -100,3 +95,5 @@ end # Also covers, e.g., `convert(_APL, ::P)` where `P<:_APL` Base.convert(::Type{PT}, p::PT) where {PT<:_APL} = p +# Disambiguation: identity conversion for AbstractPolynomialLike +Base.convert(::Type{PT}, p::PT) where {PT<:AbstractPolynomialLike} = p diff --git a/src/default_term.jl b/src/default_term.jl index 2ed44b7c..ed8040e5 100644 --- a/src/default_term.jl +++ b/src/default_term.jl @@ -1,10 +1,8 @@ """ - struct Term{CoeffType,M<:AbstractMonomial} <: AbstractTerm{CoeffType} - coefficient::CoeffType - monomial::M - end + Term{CoeffType,M} -A representation of the multiplication between a `coefficient` and a `monomial`. +Type alias for [`StarAlgebras.Term{CoeffType,M}`](@ref). +A representation of the multiplication between a `coefficient` and a monomial. !!! note The `coefficient` does not need to be a `Number`. It can be for instance a @@ -16,116 +14,103 @@ A representation of the multiplication between a `coefficient` and a `monomial`. multiply each term of `p` with `m` but `term(p, m)` will create a term with `p` as coefficient and `m` as monomial. """ -struct Term{CoeffType,M<:AbstractMonomial} <: AbstractTerm{CoeffType} - coefficient::CoeffType - monomial::M -end +const Term{CoeffType,M} = SA.Term{CoeffType,M} -coefficient(t::Term) = t.coefficient -monomial(t::Term) = t.monomial -term_type(::Type{<:Term{C,M}}, ::Type{T}) where {C,M,T} = Term{T,M} -monomial_type(::Type{<:Term{C,M}}) where {C,M} = M +monomial_type(::Type{<:SA.Term{<:Any,M}}) where {M} = M -(t::Term)(s...) = substitute(Eval(), t, s) +(t::SA.Term)(s...) = substitute(Eval(), t, s) -function Base.convert(::Type{Term{T,M}}, m::AbstractMonomialLike) where {T,M} - return Term(one(T), convert(M, m)) -end -function Base.convert(::Type{Term{T,M}}, t::AbstractTerm) where {T,M} - return Term{T,M}(convert(T, coefficient(t)), convert(M, monomial(t))) -end -function Base.convert(::Type{Term{T,M}}, t::Term{T,M}) where {T,M} - return t +# convert from AbstractMonomialLike (MP type in signature, not piracy) +function Base.convert(::Type{SA.Term{T,M}}, m::AbstractMonomialLike) where {T,M} + return SA.Term(one(T), convert(M, m)) end -function convert_constant(::Type{Term{C,M} where C}, α) where {M} - return convert(Term{typeof(α),M}, α) -end +# convert, promote_rule, ==, isequal, hash, copy, ^, ndims, broadcastable +# for SA.Term × SA.Term are defined in StarAlgebras (not here, to avoid piracy) +# promote_rule involving MP's AbstractMonomialLike (not piracy) function Base.promote_rule( - ::Type{Term{C,M1} where {C}}, + ::Type{SA.Term{C,M1} where {C}}, M2::Type{<:AbstractMonomialLike}, ) where {M1} - return (Term{C,promote_type(M1, M2)} where {C}) + return (SA.Term{C,promote_type(M1, M2)} where {C}) end function Base.promote_rule( M1::Type{<:AbstractMonomialLike}, - ::Type{Term{C,M2} where {C}}, + ::Type{SA.Term{C,M2} where {C}}, ) where {M2} - return (Term{C,promote_type(M1, M2)} where {C}) + return (SA.Term{C,promote_type(M1, M2)} where {C}) end +# promote_rule with UnionAll Term type (involves `where C` which is MP convention) function Base.promote_rule( - ::Type{Term{C,M1} where {C}}, - ::Type{Term{T,M2}}, + ::Type{SA.Term{C,M1} where {C}}, + ::Type{SA.Term{T,M2}}, ) where {T,M1,M2} - return (Term{C,promote_type(M1, M2)} where {C}) + return (SA.Term{C,promote_type(M1, M2)} where {C}) end function Base.promote_rule( - ::Type{Term{T,M2}}, - ::Type{Term{C,M1} where {C}}, + ::Type{SA.Term{T,M2}}, + ::Type{SA.Term{C,M1} where {C}}, ) where {T,M1,M2} - return (Term{C,promote_type(M1, M2)} where {C}) + return (SA.Term{C,promote_type(M1, M2)} where {C}) +end +promote_rule_constant(::Type{T}, TT::Type{SA.Term{C,M} where C}) where {T,M} = Any + +function convert_constant(::Type{SA.Term{C,M} where C}, α) where {M} + return convert(SA.Term{typeof(α),M}, α) end -promote_rule_constant(::Type{T}, TT::Type{Term{C,M} where C}) where {T,M} = Any -combine(t1::Term, t2::Term) = combine(promote(t1, t2)...) -function combine(t1::T, t2::T) where {T<:Term} - return Term(t1.coefficient + t2.coefficient, t1.monomial) +combine(t1::SA.Term, t2::SA.Term) = combine(promote(t1, t2)...) +function combine(t1::T, t2::T) where {T<:SA.Term} + return SA.Term(coefficient(t1) + coefficient(t2), monomial(t1)) end function MA.promote_operation( ::typeof(combine), - ::Type{Term{S,M1}}, - ::Type{Term{T,M2}}, + ::Type{SA.Term{S,M1}}, + ::Type{SA.Term{T,M2}}, ) where {S,T,M1,M2} - return Term{MA.promote_operation(+, S, T),promote_type(M1, M2)} + return SA.Term{MA.promote_operation(+, S, T),promote_type(M1, M2)} end -function MA.mutability(::Type{Term{C,M}}) where {C,M} - if MA.mutability(C) isa MA.IsMutable && MA.mutability(M) isa MA.IsMutable - return MA.IsMutable() - else - return MA.IsNotMutable() - end -end +# MA.mutability, Base.copy, MA.mutable_copy, MA.operate_to!(*, Term, Term, Term), +# MA.operate!(*, Term, Term), and MA.operate!(one, Term) are defined in StarAlgebras. -# `Base.power_by_squaring` calls `Base.copy` and we want -# `t^1` to be a mutable copy of `t` so `copy` needs to be -# the same as `mutable_copy`. -Base.copy(t::Term) = MA.mutable_copy(t) -function MA.mutable_copy(t::Term) - return Term( - MA.copy_if_mutable(coefficient(t)), - MA.copy_if_mutable(monomial(t)), - ) +# Broader MA operations involving MP's AbstractTermLike (not piracy) +function MA.operate_to!( + t::SA.Term, + ::typeof(*), + t1::AbstractMonomialLike, + t2::AbstractTermLike, +) + MA.operate_to!(t.coefficient, *, coefficient(t1), coefficient(t2)) + MA.operate_to!(t.basis_element, *, monomial(t1), monomial(t2)) + return t end - function MA.operate_to!( - t::Term, + t::SA.Term, ::typeof(*), t1::AbstractTermLike, - t2::AbstractTermLike, + t2::AbstractMonomialLike, ) MA.operate_to!(t.coefficient, *, coefficient(t1), coefficient(t2)) - MA.operate_to!(t.monomial, *, monomial(t1), monomial(t2)) + MA.operate_to!(t.basis_element, *, monomial(t1), monomial(t2)) return t end - -function MA.operate!(::typeof(*), t1::Term, t2::AbstractTermLike) - MA.operate!(*, t1.coefficient, coefficient(t2)) - MA.operate!(*, t1.monomial, monomial(t2)) - return t1 +function MA.operate_to!( + t::SA.Term, + ::typeof(*), + t1::AbstractMonomialLike, + t2::AbstractMonomialLike, +) + MA.operate_to!(t.coefficient, *, coefficient(t1), coefficient(t2)) + MA.operate_to!(t.basis_element, *, monomial(t1), monomial(t2)) + return t end # Needed to resolve ambiguity with # MA.operate!(::typeof(*), ::AbstractMonomial, ::AbstractMonomialLike) -function MA.operate!(::typeof(*), t1::Term, t2::AbstractMonomialLike) +function MA.operate!(::typeof(*), t1::SA.Term, t2::AbstractMonomialLike) MA.operate!(*, t1.coefficient, coefficient(t2)) - MA.operate!(*, t1.monomial, monomial(t2)) + MA.operate!(*, t1.basis_element, monomial(t2)) return t1 end - -function MA.operate!(::typeof(one), t::Term) - MA.operate!(one, t.coefficient) - MA.operate!(constant_monomial, t.monomial) - return t -end diff --git a/src/det.jl b/src/det.jl index f39d77f6..9f9861ed 100644 --- a/src/det.jl +++ b/src/det.jl @@ -1,4 +1,4 @@ -function LinearAlgebra.det(M::Matrix{<:AbstractPolynomialLike}) +function LinearAlgebra.det(M::Matrix{<:_APL}) m = size(M)[1] if m > 2 return sum( diff --git a/src/gcd.jl b/src/gcd.jl index 4d7f16ab..33d60206 100644 --- a/src/gcd.jl +++ b/src/gcd.jl @@ -129,7 +129,7 @@ function MA.promote_operation( end """ - function gcd(p1::AbstractPolynomialLike{T}, p2::AbstractPolynomialLike{S}) where {T, S} + function gcd(p1::_APL{T}, p2::_APL{S}) where {T, S} Returns a greatest common divisor of `p1` and `p2`. Note that it does not make sense, in general, to speak of "the" greatest common divisor of u and v; there @@ -225,7 +225,7 @@ function Base.gcd( ) end -function shift_deflation(p::AbstractPolynomialLike, v::AbstractVariable) +function shift_deflation(p::_APL, v::AbstractVariable) shift = -1 defl = 0 for mono in monomials(p) @@ -254,7 +254,7 @@ function shift_deflation(p::AbstractPolynomialLike, v::AbstractVariable) end # Inspired from to `AbstractAlgebra.deflation` -function deflation(p::AbstractPolynomialLike) +function deflation(p::_APL) if iszero(p) return constant_monomial(p), constant_monomial(p) end @@ -272,7 +272,7 @@ function _zero_to_one_exp(defl::AbstractMonomial) variables(defl) .^ map(d -> iszero(d) ? one(d) : d, exponents(defl)), ) end -function deflate(p::AbstractPolynomialLike, shift, defl) +function deflate(p::_APL, shift, defl) if isconstant(shift) && all(d -> isone(d) || iszero(d), exponents(defl)) return p end @@ -282,7 +282,7 @@ end function inflate(α, shift, defl) return inflate(convert(polynomial_type(shift, typeof(α)), α), shift, defl) end -function inflate(p::AbstractPolynomialLike, shift, defl) +function inflate(p::_APL, shift, defl) if isconstant(shift) && all(d -> isone(d) || iszero(d), exponents(defl)) return p end @@ -302,7 +302,15 @@ end # Inspired from to `AbstractAlgebra.deflate` function MA.operate( op::Union{typeof(deflate),typeof(inflate)}, - p::AbstractPolynomialLike, + t::SA.Term, + shift, + defl, +) + return term(coefficient(t), MA.operate(op, monomial(t), shift, defl)) +end +function MA.operate( + op::Union{typeof(deflate),typeof(inflate)}, + p::_APL, shift, defl, ) @@ -722,7 +730,7 @@ function primitive_univariate_gcdx(p::_APL, q::_APL, ::SubresultantAlgorithm) end """ - univariate_gcd(p1::AbstractPolynomialLike, p2::AbstractPolynomialLike, algo::AbstractUnivariateGCDAlgorithm) + univariate_gcd(p1::_APL, p2::_APL, algo::AbstractUnivariateGCDAlgorithm) Return the *greatest common divisor* of the polynomials `p1` and `p2` that have at most one variable in common and for which the coefficients are either @@ -857,7 +865,7 @@ function termwise_content(p::_APL, algo, mutability::MA.MutableTrait) end """ - content(poly::AbstractPolynomialLike{T}, algo::AbstractUnivariateGCDAlgorithm, mutability::MA.MutableTrait) where {T} + content(poly::_APL{T}, algo::AbstractUnivariateGCDAlgorithm, mutability::MA.MutableTrait) where {T} Return the *content* of the polynomial `poly` over a unique factorization domain `S` as defined in [Knu14, (3) p. 423]. @@ -927,7 +935,7 @@ function content( end """ - primitive_part(poly::AbstractPolynomialLike{T}, algo::AbstractUnivariateGCDAlgorithm) where {T} + primitive_part(poly::_APL{T}, algo::AbstractUnivariateGCDAlgorithm) where {T} Return the *primitive part* of the polynomial `poly` over a unique factorization domain `S` as defined in [Knu14, (3) p. 423]. @@ -962,7 +970,7 @@ function primitive_part( end """ - primitive_part_content(poly::AbstractPolynomialLike{T}, algo::AbstractUnivariateGCDAlgorithm) where {T} + primitive_part_content(poly::_APL{T}, algo::AbstractUnivariateGCDAlgorithm) where {T} Return the *primitive part* and *content* of the polynomial `poly` over a unique factorization domain `S` as defined in [Knu14, (3) p. 423]. This is more diff --git a/src/hash.jl b/src/hash.jl index 7ec79a5a..1eb7d127 100644 --- a/src/hash.jl +++ b/src/hash.jl @@ -15,17 +15,7 @@ function Base.hash(m::AbstractMonomial, u::UInt) end end -function Base.hash(t::AbstractTerm, u::UInt) - if iszero(t) - hash(0, u) - elseif isone(coefficient(t)) - # We want the has of `t` to match the hash of `monomial(t)` - # so we ignore the coefficient one. - hash(monomial(t), u) - else - hash(monomial(t), hash(coefficient(t), u)) - end -end +# hash for SA.Term is defined in StarAlgebras function Base.hash(p::AbstractPolynomial, u::UInt) if iszero(p) diff --git a/src/monomial.jl b/src/monomial.jl index 3dd6153a..04fde627 100644 --- a/src/monomial.jl +++ b/src/monomial.jl @@ -8,6 +8,9 @@ Return the type of the monomials of `p`. Returns the type of the monomials of a polynomial of type `PT`. """ monomial_type(::Union{M,Type{M}}) where {M<:AbstractMonomial} = M +# AbstractMonomialLike is its own monomial type (like AbstractMonomial). +# This breaks the term_type/monomial_type cycle for this bare abstract type. +monomial_type(::Union{AbstractMonomialLike,Type{AbstractMonomialLike}}) = AbstractMonomialLike function monomial_type(::Union{PT,Type{PT}}) where {PT<:_APL} return monomial_type(term_type(PT)) end @@ -158,6 +161,11 @@ Base.one(t::AbstractMonomialLike) = constant_monomial(t) function MA.promote_operation(::typeof(one), MT::Type{<:AbstractMonomialLike}) return monomial_type(MT) end +# Bridge MA.operate!(one, ...) to constant_monomial for monomials +# so that SA.Term's generic operate!(one, t) works via operate!(one, t.basis_element) +function MA.operate!(::typeof(one), m::AbstractMonomial) + return MA.operate!(constant_monomial, m) +end # See https://github.com/JuliaAlgebra/MultivariatePolynomials.jl/issues/82 # By default, Base do oneunit(v::VT) = VT(one(v)). # This tries to convert a monomial to a variable which does not work. diff --git a/src/operators.jl b/src/operators.jl index b11bb783..2c150274 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -26,6 +26,7 @@ function Base.isapprox(t1::AbstractTermLike, t2::AbstractTermLike; kwargs...) return isapprox(coefficient(t1), coefficient(t2); kwargs...) && monomial(t1) == monomial(t2) end +# dot for SA.Term is defined in StarAlgebras function Base.isapprox(p1::_APL, p2::_APL; kwargs...) return isapprox(promote(p1, p2)...; kwargs...) @@ -82,6 +83,10 @@ end function MA.operate_to!(output::AbstractPolynomial, op::typeof(*), p::_APL, α) return MA.operate_to!(output, right_constant_mult, p, α) end +# Disambiguation: both args are _APL — use polynomial multiplication +function MA.operate_to!(output::AbstractPolynomial, ::typeof(*), p::_APL, q::_APL) + return MA.operate_to!(output, *, polynomial(p), polynomial(q)) +end function MA.operate_to!(output::_APL, op::typeof(/), p::_APL, α) return map_coefficients_to!(output, Base.Fix2(op, α), p) end @@ -288,6 +293,13 @@ left_constant_mult(α, v::AbstractMonomial) = term_type(v, typeof(α))(α, v) left_constant_mult(α, v::AbstractVariable) = left_constant_mult(α, monomial(v)) # TODO linear term right_constant_mult(m::AbstractMonomialLike, α) = left_constant_mult(α, m) +function left_constant_mult(α, t::SA.Term) + return term(α * coefficient(t), monomial(t)) +end +function right_constant_mult(t::SA.Term, α) + return term(coefficient(t) * α, monomial(t)) +end + function left_constant_mult(α, p::AbstractPolynomialLike) return map_coefficients(Base.Fix1(*, α), p) end @@ -354,7 +366,7 @@ function _polynomial_2terms( t1::TT, t2::TT, ::Type{T}, -) where {TT<:AbstractTerm,T} +) where {TT<:SA.Term,T} if iszero(t1) polynomial(t2, T) elseif iszero(t2) @@ -372,10 +384,10 @@ for op in [:+, :-] function Base.$op(t1::AbstractTermLike, t2::AbstractTermLike) return $op(term(t1), term(t2)) end - function Base.$op(t1::AbstractTerm, t2::AbstractTerm) + function Base.$op(t1::SA.Term, t2::SA.Term) return $op(_promote_terms(t1, t2)...) end - function Base.$op(t1::TT, t2::TT) where {T,TT<:AbstractTerm{T}} + function Base.$op(t1::TT, t2::TT) where {T,TT<:SA.Term{T}} S = MA.promote_operation($op, T, T) # t1 > t2 would compare the coefficient in case the monomials are equal # and it will throw a MethodError in case the coefficients are not comparable @@ -395,21 +407,21 @@ end _promote_terms(t1, t2) = promote(t1, t2) # Promotion between `I` and `1` is `Any`. function _promote_terms( - t1::AbstractTerm, - t2::AbstractTerm{<:LinearAlgebra.UniformScaling}, + t1::SA.Term, + t2::SA.Term{<:LinearAlgebra.UniformScaling}, ) return _promote_terms(t1, coefficient(t2).λ * monomial(t2)) end function _promote_terms( - t1::AbstractTerm{<:LinearAlgebra.UniformScaling}, - t2::AbstractTerm, + t1::SA.Term{<:LinearAlgebra.UniformScaling}, + t2::SA.Term, ) return _promote_terms(coefficient(t1).λ * monomial(t1), t2) end # Promotion between `I` and `2I` is `UniformScaling`, not `UniformScaling{Int}`. function _promote_terms( - t1::AbstractTerm{LinearAlgebra.UniformScaling{S}}, - t2::AbstractTerm{LinearAlgebra.UniformScaling{T}}, + t1::SA.Term{LinearAlgebra.UniformScaling{S}}, + t2::SA.Term{LinearAlgebra.UniformScaling{T}}, ) where {S<:Number,T<:Number} U = LinearAlgebra.UniformScaling{promote_type(S, T)} return _promote_terms( @@ -418,15 +430,15 @@ function _promote_terms( ) end function _promote_terms( - t1::AbstractTerm{LinearAlgebra.UniformScaling{T}}, - t2::AbstractTerm{LinearAlgebra.UniformScaling{T}}, + t1::SA.Term{LinearAlgebra.UniformScaling{T}}, + t2::SA.Term{LinearAlgebra.UniformScaling{T}}, ) where {T<:Number} return promote(t1, t2) end LinearAlgebra.adjoint(v::AbstractVariable) = conj(v) LinearAlgebra.adjoint(m::AbstractMonomial) = conj(m) -function LinearAlgebra.adjoint(t::AbstractTerm) +function LinearAlgebra.adjoint(t::SA.Term) return _term(adjoint(coefficient(t)), adjoint(monomial(t))) end function LinearAlgebra.adjoint(p::AbstractPolynomialLike) @@ -447,7 +459,7 @@ LinearAlgebra.ishermitian(p::AbstractPolynomialLike) = p == conj(p) LinearAlgebra.transpose(v::AbstractVariable) = v LinearAlgebra.transpose(m::AbstractMonomial) = m -function LinearAlgebra.transpose(t::AbstractTerm) +function LinearAlgebra.transpose(t::SA.Term) return _term(LinearAlgebra.transpose(coefficient(t)), monomial(t)) end function LinearAlgebra.transpose(p::AbstractPolynomialLike) @@ -480,6 +492,7 @@ Base.vec(vars::Tuple{Vararg{AbstractVariable}}) = [vars...] # https://github.com/JuliaLang/julia/pull/23332 Base.:^(x::AbstractPolynomialLike, p::Integer) = Base.power_by_squaring(x, p) +# ^(::SA.Term, ::Integer) is defined in StarAlgebras function MA.operate_to!( output::AbstractPolynomial, diff --git a/src/polynomial.jl b/src/polynomial.jl index be268675..009821df 100644 --- a/src/polynomial.jl +++ b/src/polynomial.jl @@ -1,4 +1,4 @@ -function LinearAlgebra.norm(p::AbstractPolynomialLike, r::Int = 2) +function LinearAlgebra.norm(p::_APL, r::Int = 2) return LinearAlgebra.norm(coefficients(p), r) end @@ -286,7 +286,7 @@ Returns the minimal degree of the monomials of `p` in the variable `v`, i.e. `mi Calling `mindegree` on on ``4x^2y + xy + 2x`` should return 1, `mindegree(4x^2y + xy + 2x, x)` should return 1 and `mindegree(4x^2y + xy + 2x, y)` should return 0. """ function mindegree( - X::AbstractVector{<:AbstractPolynomialLike}, + X::AbstractVector{<:_APL}, args::Vararg{Any,N}, ) where {N} return isempty(X) ? 0 : minimum(t -> mindegree(t, args...), X) @@ -312,7 +312,7 @@ Returns the maximal degree of the monomials of `p` in the variable `v`, i.e. `ma Calling `maxdegree` on ``4x^2y + xy + 2x`` should return 3, `maxdegree(4x^2y + xy + 2x, x)` should return 2 and `maxdegree(4x^2y + xy + 2x, y)` should return 1. """ function maxdegree( - X::AbstractVector{<:AbstractPolynomialLike}, + X::AbstractVector{<:_APL}, args::Vararg{Any,N}, ) where {N} return mapreduce(t -> maxdegree(t, args...), max, X, init = 0) @@ -634,7 +634,7 @@ function map_coefficients_to! end Return `deg, num` where `deg = maxdegree(p, var)` and `num` is the number of terms `t` such that `degree(t, var) == deg`. """ -function deg_num_leading_terms(p::AbstractPolynomialLike, var) +function deg_num_leading_terms(p::_APL, var) deg = 0 num = 0 for mono in monomials(p) @@ -673,4 +673,5 @@ function multiplication_preserves_monomial_order( end Base.ndims(::Union{Type{<:AbstractPolynomialLike},AbstractPolynomialLike}) = 0 +# ndims and broadcastable for SA.Term are defined in StarAlgebras Base.broadcastable(p::AbstractPolynomialLike) = Ref(p) diff --git a/src/promote.jl b/src/promote.jl index 7fdc56cf..349b652d 100644 --- a/src/promote.jl +++ b/src/promote.jl @@ -14,19 +14,50 @@ function Base.promote_rule( return promote_type(monomial_type(M1), monomial_type(M2)) end -# TermLike -Base.promote_rule(::Type{T}, ::Type{T}) where {T<:AbstractTermLike} = T +# SA.Term +Base.promote_rule(::Type{T}, ::Type{T}) where {T<:SA.Term} = T function Base.promote_rule( - TS::Type{<:AbstractTermLike{S}}, - TT::Type{<:AbstractTermLike{T}}, + TS::Type{<:SA.Term{S}}, + TT::Type{<:SA.Term{T}}, ) where {S,T} U = promote_type(S, T) M = promote_type(monomial_type(TS), monomial_type(TT)) return term_type(M, U) end +# MonomialLike-Term promote +function Base.promote_rule( + TS::Type{<:AbstractMonomialLike}, + TT::Type{<:SA.Term{T}}, +) where {T} + U = promote_type(Int, T) + M = promote_type(monomial_type(TS), monomial_type(TT)) + return term_type(M, U) +end +function Base.promote_rule( + TT::Type{<:SA.Term{T}}, + TS::Type{<:AbstractMonomialLike}, +) where {T} + U = promote_type(T, Int) + M = promote_type(monomial_type(TT), monomial_type(TS)) + return term_type(M, U) +end +# Bare AbstractMonomialLike: we can't compute monomial_type/term_type on it, +# so return the abstract union type directly. +function Base.promote_rule( + ::Type{AbstractMonomialLike}, + ::Type{<:SA.Term{T}}, +) where {T} + return AbstractTermLike{promote_type(Int, T)} +end +function Base.promote_rule( + ::Type{<:SA.Term{T}}, + ::Type{AbstractMonomialLike}, +) where {T} + return AbstractTermLike{promote_type(T, Int)} +end function promote_rule_constant( ::Type{S}, - TT::Type{<:AbstractTermLike{T}}, + TT::Type{<:SA.Term{T}}, ) where {S,T} return term_type(TT, promote_type(S, T)) end @@ -34,11 +65,35 @@ end # PolynomialLike Base.promote_rule(::Type{PT}, ::Type{PT}) where {PT<:_APL} = PT function Base.promote_rule(PS::Type{<:_APL}, PT::Type{<:_APL}) - return polynomial_type(promote_type(term_type(PS), term_type(PT))) + # For abstract or UnionAll types (e.g. SA.Term{T,M} where T from + # promote_typejoin), term_type/polynomial_type may not work. + # Return Union{} so Julia falls back to typejoin. + TS = try + term_type(PS) + catch + return Union{} + end + TT = try + term_type(PT) + catch + return Union{} + end + return polynomial_type(promote_type(TS, TT)) +end +# When one side is the bare AbstractMonomialLike type, avoid calling term_type on it +function Base.promote_rule(::Type{AbstractMonomialLike}, PT::Type{<:_APL{T}}) where {T} + return _apl(Int, T) +end +function Base.promote_rule(PT::Type{<:_APL{T}}, ::Type{AbstractMonomialLike}) where {T} + return _apl(Int, T) end function promote_rule_constant(::Type{S}, PT::Type{<:_APL{T}}) where {S,T} - return polynomial_type(PT, promote_type(S, T)) + try + return polynomial_type(PT, promote_type(S, T)) + catch + return Any + end end function Base.promote_rule(::Type{PT}, ::Type{T}) where {T,PT<:_APL} return promote_rule_constant(T, PT) @@ -46,11 +101,14 @@ end # We don't have any information on the MultivariatePolynomials implementation, # so we won't be able to convert the constant to `_APL`. -promote_rule_constant(::Type, PT::Type{AbstractMonomialLike}) = Any -promote_rule_constant(::Type, PT::Type{AbstractTermLike{T}}) where {T} = Any -promote_rule_constant(::Type, PT::Type{AbstractTermLike}) = Any -promote_rule_constant(::Type, PT::Type{_APL{T}}) where {T} = Any -promote_rule_constant(::Type, PT::Type{_APL}) = Any +# These exact-type matches must be listed explicitly because the generic +# promote_rule_constant(::Type{S}, ::Type{<:_APL{T}}) above would try to call +# polynomial_type/term_type on these abstract/union types and fail. +promote_rule_constant(::Type, ::Type{AbstractMonomialLike}) = Any +promote_rule_constant(::Type, ::Type{AbstractPolynomialLike{T}}) where {T} = Any +promote_rule_constant(::Type, ::Type{AbstractPolynomialLike}) = Any +promote_rule_constant(::Type, ::Type{_APL{T}}) where {T} = Any +promote_rule_constant(::Type, ::Type{_APL}) = Any # AbstractMonomialLike{T} function Base.promote_rule( @@ -65,108 +123,8 @@ function Base.promote_rule( ) return AbstractMonomialLike end -function Base.promote_rule( - ::Type{AbstractMonomialLike}, - ::Type{<:AbstractTermLike{T}}, -) where {T} - return _atl(Int, T) -end -function Base.promote_rule( - ::Type{<:AbstractTermLike{T}}, - ::Type{AbstractMonomialLike}, -) where {T} - return _atl(Int, T) -end -function Base.promote_rule( - ::Type{AbstractMonomialLike}, - ::Type{AbstractTermLike{T}}, -) where {T} - return _atl(Int, T) -end -function Base.promote_rule( - ::Type{AbstractTermLike{T}}, - ::Type{AbstractMonomialLike}, -) where {T} - return _atl(Int, T) -end -function Base.promote_rule( - ::Type{AbstractMonomialLike}, - ::Type{<:_APL{T}}, -) where {T} - return _apl(Int, T) -end -function Base.promote_rule( - ::Type{<:_APL{T}}, - ::Type{AbstractMonomialLike}, -) where {T} - return _apl(Int, T) -end -function Base.promote_rule( - ::Type{AbstractMonomialLike}, - ::Type{_APL{T}}, -) where {T} - return _apl(Int, T) -end -function Base.promote_rule( - ::Type{_APL{T}}, - ::Type{AbstractMonomialLike}, -) where {T} - return _apl(Int, T) -end - -# AbstractTermLike{T} -_atl(::Type{T}, ::Type{T}) where {T} = AbstractTermLike{T} -_atl(::Type, ::Type) = AbstractTermLike -__atl(::Type{T}, ::Type{<:AbstractTermLike{S}}) where {S,T} = _atl(T, S) -__atl(::Type{T}, ::Type{<:_APL{S}}) where {S,T} = _apl(T, S) -function Base.promote_rule( - ::Type{AbstractTermLike{T}}, - P::Type{<:AbstractTermLike{S}}, -) where {S,T} - return _atl(T, S) -end -function Base.promote_rule( - P::Type{<:AbstractTermLike{S}}, - ::Type{AbstractTermLike{T}}, -) where {S,T} - return _atl(T, S) -end -function Base.promote_rule( - ::Type{AbstractTermLike{T}}, - P::Type{<:_APL{S}}, -) where {S,T} - return _apl(T, S) -end -function Base.promote_rule( - P::Type{<:_APL{S}}, - ::Type{AbstractTermLike{T}}, -) where {S,T} - return _apl(T, S) -end -function Base.promote_rule( - ::Type{AbstractTermLike{T}}, - P::Type{_APL{S}}, -) where {S,T} - return _apl(T, S) -end -function Base.promote_rule( - P::Type{_APL{S}}, - ::Type{AbstractTermLike{T}}, -) where {S,T} - return _apl(T, S) -end - -# AbstractTermLike -function Base.promote_rule(::Type{AbstractTermLike}, ::Type{<:AbstractTermLike}) - return AbstractTermLike -end -function Base.promote_rule(::Type{<:AbstractTermLike}, ::Type{AbstractTermLike}) - return AbstractTermLike -end -Base.promote_rule(::Type{AbstractTermLike}, ::Type{<:_APL}) = _APL -Base.promote_rule(::Type{<:_APL}, ::Type{AbstractTermLike}) = _APL -Base.promote_rule(::Type{AbstractTermLike}, ::Type{_APL}) = _APL -Base.promote_rule(::Type{_APL}, ::Type{AbstractTermLike}) = _APL +# AbstractMonomialLike vs _APL and AbstractTermLike promote rules are +# handled above (lines 69-75 and the AbstractMonomialLike-SA.Term rules). # _APL{T} _apl(::Type{T}, ::Type{T}) where {T} = _APL{T} @@ -243,13 +201,31 @@ function MA.promote_operation( end function MA.promote_operation( ::typeof(*), - TT::Type{<:AbstractTermLike{S}}, - ST::Type{<:AbstractTermLike{T}}, + TT::Type{<:SA.Term{S}}, + ST::Type{<:SA.Term{T}}, ) where {S,T} UT = MA.promote_operation(*, monomial_type(TT), monomial_type(ST)) U = MA.promote_operation(*, S, T) return promote_operation_left_constant(*, U, UT) end +function MA.promote_operation( + ::typeof(*), + TT::Type{<:AbstractMonomialLike}, + ST::Type{<:SA.Term{T}}, +) where {T} + UT = MA.promote_operation(*, monomial_type(TT), monomial_type(ST)) + U = MA.promote_operation(*, Int, T) + return promote_operation_left_constant(*, U, UT) +end +function MA.promote_operation( + ::typeof(*), + TT::Type{<:SA.Term{S}}, + ST::Type{<:AbstractMonomialLike}, +) where {S} + UT = MA.promote_operation(*, monomial_type(TT), monomial_type(ST)) + U = MA.promote_operation(*, S, Int) + return promote_operation_left_constant(*, U, UT) +end function MA.promote_operation( ::typeof(*), PT::Type{<:_APL{S}}, diff --git a/src/term.jl b/src/term.jl index f9cf0866..f8b07c43 100644 --- a/src/term.jl +++ b/src/term.jl @@ -43,9 +43,18 @@ Returns the type of the terms of `p` but with coefficient type `T`. Returns the type of the terms of a polynomial of type `PT` but with coefficient type `T`. """ -term_type(::Type{T}) where {T<:AbstractTerm} = T +term_type(::Type{<:SA.Term{T,B}}) where {T,B} = SA.Term{T,B} +# Handle UnionAll types like SA.Term{T,M} where T (from promote_typejoin) +term_type(::Type{<:SA.Term{<:Any,B}}) where {B} = SA.Term{<:Any,B} +term_type(::Type{<:SA.Term{<:Any,B}}, ::Type{T}) where {T,B} = SA.Term{T,B} term_type(p::Type{<:_APL}, ::Type{T}) where {T} = term_type(term_type(p), T) term_type(::Type{M}) where {M<:AbstractMonomialLike} = term_type(M, Int) +# Break the term_type/monomial_type cycle for bare AbstractMonomialLike: +# term_type(AbstractMonomialLike, T) should return SA.Term{T, AbstractMonomialLike} +# rather than recursing through the generic _APL 2-arg path. +function term_type(::Type{AbstractMonomialLike}, ::Type{T}) where {T} + return SA.Term{T,AbstractMonomialLike} +end term_type(v::Type{<:AbstractVariable}) = term_type(monomial_type(v)) function term_type(v::Type{<:AbstractVariable}, ::Type{T}) where {T} return term_type(monomial_type(v), T) @@ -80,7 +89,7 @@ Calling `coefficient(2x + 4y^2 + 3, y^2)` should return ``4``. Calling `coefficient(2x + 4y^2 + 3, x^2)` should return ``0``. """ function coefficient end -coefficient(t::AbstractTerm) = t.coefficient # by convention, the field should be `coefficient` +coefficient(t::SA.Term) = SA.coefficient(t) coefficient(m::AbstractMonomialLike) = 1 function coefficient( p::AbstractPolynomialLike{T}, @@ -138,10 +147,14 @@ calling `coefficient_type` on ``xy`` should return `Int`. """ function coefficient_type( ::Union{PT,Type{PT},AbstractVector{PT},Type{<:AbstractVector{PT}}}, -) where {T,PT<:_APL{T}} +) where {T,PT<:AbstractPolynomialLike{T}} + return T +end +function coefficient_type( + ::Union{SA.Term{T,B},Type{SA.Term{T,B}}}, +) where {T,B} return T end -#coefficient_type(::{T, Type{T}}) where {T} = T """ monomial(t::AbstractTermLike) @@ -164,7 +177,7 @@ In order to create `x^2 * y`, * with TypedPolynomials, use `monomial((x, y), (2, 1))`. """ function monomial end -monomial(t::AbstractTerm) = t.monomial # by convention, the field should be `monomial`. +monomial(t::SA.Term) = SA.basis_element(t) monomial(m::AbstractMonomial) = m """ @@ -192,22 +205,32 @@ Equivalent to `constant_term(zero(T), p)`. Equivalent to `constant_term(zero(T), PT)`. """ -zero_term(::Type{PT}) where {T,PT<:_APL{T}} = constant_term(zero(T), PT) -zero_term(p::_APL{T}) where {T} = constant_term(zero(T), p) +zero_term(::Type{PT}) where {T,PT<:AbstractPolynomialLike{T}} = constant_term(zero(T), PT) +zero_term(p::AbstractPolynomialLike{T}) where {T} = constant_term(zero(T), p) +zero_term(::Type{SA.Term{T,B}}) where {T,B} = constant_term(zero(T), SA.Term{T,B}) +zero_term(t::SA.Term) = constant_term(zero(coefficient(t)), t) -function Base.zero(::Type{TT}) where {T,TT<:AbstractTermLike{T}} +function Base.zero(::Type{TT}) where {TT<:AbstractMonomialLike} return zero(polynomial_type(TT)) end -Base.zero(t::AbstractTermLike{T}) where {T} = zero(polynomial_type(t)) -function MA.promote_operation(::typeof(zero), PT::Type{<:AbstractTermLike}) +function Base.zero(t::AbstractMonomialLike) + return zero(polynomial_type(t)) +end +function MA.promote_operation(::typeof(zero), PT::Type{<:AbstractMonomialLike}) return polynomial_type(PT) end -function Base.one(::Type{TT}) where {T,TT<:AbstractTermLike{T}} - return term(one(T), constant_monomial(TT)) +# SA.Term already defines `zero(t::Term)` and `one(t::Term)` in StarAlgebras. +# For type-level zero, return a polynomial (for summation efficiency): +function Base.zero(::Type{SA.Term{T,M}}) where {T,M} + return zero(polynomial_type(SA.Term{T,M})) +end +function MA.promote_operation(::typeof(zero), PT::Type{<:SA.Term}) + return polynomial_type(PT) end -Base.one(t::AbstractTermLike{T}) where {T} = term(one(T), constant_monomial(t)) -function MA.promote_operation(::typeof(one), TT::Type{<:AbstractTermLike}) - return term_type(TT) +# `one` and `MA.promote_operation(one, ...)` for monomials is defined in monomial.jl +# `one(t::SA.Term)` and `one(::Type{SA.Term{T,M}})` are defined in StarAlgebras +function MA.promote_operation(::typeof(one), ::Type{SA.Term{T,M}}) where {T,M} + return SA.Term{T,M} end function MA.promote_operation(::typeof(one), PT::Type{<:AbstractPolynomialLike}) return polynomial_type(PT) diff --git a/test/commutative/promote.jl b/test/commutative/promote.jl index 6c5ad67b..fee9a372 100644 --- a/test/commutative/promote.jl +++ b/test/commutative/promote.jl @@ -55,34 +55,7 @@ using LinearAlgebra return _t(a, 1.0x + y, PT()) end - function _test(a, af, TT) - __test(a, apl, TT, TT) - # `x isa _APL{Int}` so here we don't have `Float64`: - pt() = apl() - pt(T) = apl() - tt() = TT() - tt(T) = TT() - __test(af, pt, tt, tt) - @test typeof(af) == Vector{TT()} - return _t(af, a, TT()) - end - - apl() = MP._APL - apl(T::Type) = MP._APL{T} - p = [i == 1 ? x + y : x for i in 1:2] - pf = [i == 1 ? 1.0x + y : x for i in 1:2] - _test(p, pf, apl) - - atl() = MP.AbstractTermLike - atl(T::Type) = MP.AbstractTermLike{T} - t = [i == 1 ? 2x : x for i in 1:2] - tf = [i == 1 ? 2.0x : x for i in 1:2] - _test(t, tf, atl) - _t(p, t, apl(Int)) - _t(pf, t, apl()) - _t(p, tf, apl()) - _t(pf, tf, apl()) - + # Test with concrete types from promote_typejoin __pt = Base.promote_typejoin(typeof(x + 2), typeof(x + 2.0)) _pt() = __pt _pt(::Type) = __pt @@ -93,13 +66,28 @@ using LinearAlgebra _tt(::Type) = __tt __test([i == 1 ? 2x : 2.0x for i in 1:2], _pt, _tt, _tt) + # Test with monomials: typejoin(x^2, x) = AbstractMonomialLike aml(args...) = MP.AbstractMonomialLike a = [i == 1 ? x^2 : x for i in 1:2] - __test(a, apl, atl, aml) - _t(a, t, atl(Int)) - _t(a, tf, atl()) - _t(a, p, apl(Int)) - _t(a, pf, apl()) + @test eltype(a) == MP.AbstractMonomialLike + + # Concrete type promote_type tests + @test promote_type(typeof(x), typeof(y)) <: AbstractMonomialLike + @test promote_type(typeof(2x), typeof(3y)) <: AbstractTerm{Int} + @test promote_type(typeof(2x), typeof(3.0y)) <: AbstractTerm{Float64} + @test promote_type(typeof(x + y), typeof(x)) <: AbstractPolynomialLike{Int} + @test promote_type(typeof(2x + y), typeof(3.0y)) <: AbstractPolynomialLike{Float64} + + # vcat with AbstractMonomialLike vectors + _t(a, 1, Any) + _t(a, x, aml(Int)) + _t(a, x^2, aml(Int)) + + # Test promote_rule for AbstractMonomialLike vs SA.Term + @test promote_type(MP.AbstractMonomialLike, typeof(2x)) == + MP.AbstractTermLike{Int} + @test promote_type(MP.AbstractMonomialLike, typeof(2.0x)) == + MP.AbstractTermLike{Float64} end struct A end