Closed andreasvarga closed 3 years ago
Bug. Sorry, let's see where that needs to be nipped.
I might have a fix, but let me ask what you would like these to return
[p q] # Error, right?
[p one(q)] # same as [p 1] with symbol of p, right?
[one(p) q] # same as [1 q] with symbol of q, right?
But if we do the last two, then what is?
[one(p) one(q)]
My "fix" would make order dependent as the symbol would come from the first constant polynomial encountered. This also occurs with one(p) + one(q)
or one(p) * one(q)
. Does that seem odd to you?
It is OK to be done in this way. Thus constants are tolerated even they have another symbol.
john verzani @.***> schrieb am Fr., 12. März 2021, 18:29:
I might have a fix, but let me ask what you would like these to return
[p q] # Error, right?
[p one(q)] # same as [p 1] with symbol of p, right?
[one(p) q] # same as [1 q] with symbol of q, right?
But if we do the last two, then what is?
[one(p) one(q)]
My "fix" would make order dependent as the symbol would come from the first constant polynomial encountered. This also occurs with one(p) + one(q) or one(p) * one(q). Does that seem odd to you?
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JuliaMath/Polynomials.jl/issues/312#issuecomment-797641027, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALJDHEF3OFE4IE3HNDZH2O3TDI6OXANCNFSM4ZCNVYTQ .
The basic fix is a generalization of this:
Base.promote_eltypeof(p::P, q::Q) where {T,X, P<: Polynomial{T,X}, S, Q<:Polynomial{S,X}} =
Polynomial{promote_type(T,S),X}
function Base.promote_eltypeof(p::P, q::Q) where {T,X, P<: Polynomial{T,X}, S,Y,Q<:Polynomial{S,Y}}
Polynomials.isconstant(p) && return Polynomial{promote_type(T,S), Y}
Polynomials.isconstant(q) && return Polynomial{promote_type(T,S), X}
error("X Y don't match")
end
Rather than ask you to go through github to try a branch, could you just execute the above and see if things work as expected for you?
Not sure this is a rabbit hole to keep chasing, but I find another method seems needed based on this experiment you should be able to try at home:
using Polynomials
using LinearAlgebra
using Test
p = Polynomial([1, 2], :x)
q = Polynomial([1, 2], :y)
[p q] # should error, instead 1×2 Array{Polynomial{Int64,X} where X,2}:
[p one(q)] # should be Array{Ppolynomial{Int64, :x}} is 1×2 Array{Polynomial{Int64,X} where X,2}:
Base.promote_eltypeof(p::P, q::Q) where {T,X, P<: Polynomial{T,X}, S, Q<:Polynomial{S,X}} =
⟒(P){promote_type(T,S),X}
function Base.promote_eltypeof(p::P, q::Q) where {T,X, P<:Polynomial{T,X}, S,Y,Q<:Polynomial{S,Y}}
Polynomials.isconstant(p) && return Polynomial{promote_type(T,S), Y}
Polynomials.isconstant(q) && return Polynomial{promote_type(T,S), X}
Polynomials.assert_same_variable(X,Y)
end
@test_throws ArgumentError [p q]
@test eltype([p one(q)]) == typeof(p)
# various constructs
[p p] # okay Array{Polynomial{Int64,:x},2}
[p; p] # okay
[p 1] # okay
[p one(q)] # okay
[one(p) one(q)] # kinda okay, has :y as a symbol
A = [p p^2; p^3 one(q)] # okay
B = [[p p^2] I] # okay
C = [A I] # okay
D = [p I] # not okay as I does not promote to Polynomial
E = [[p p]; [1 1]] # okay
F = [[p p]; [one(p) one(p)]] # okay
G = [[p p]; [one(p) one(q)]] # not okay; [one(p) one(q)] has symbol :y so type if off
H = [[p p]; [q q]] # not okay Array{Array{T,2} where T,1}:
function Base.promote_eltype(p::PP, q::QQ) where {T,X,N, P<:Polynomial{T,X}, PP<:Array{P,N},
S,Y, Q<:Polynomial{S,Y}, QQ<:Array{Q,N}}
Polynomials.assert_same_variable(X,Y)
⟒(P){promote_type(T,S),X}
end
H = [[p p]; [q q]] # not okay Array{Array{T,2} where T,1}:
@test_throws ArgumentError [[p p]; [q q]]
@test_throws ArgumentError [[p p]; [one(q) one(q)]]
[[p p] [ 1 one(p)]]
Are there other combinations I should also be looking at?
I found the following errors:
julia> [p q 1]
1×3 Array{Polynomial{Int64,X} where X,2}:
Polynomial(1 + 2*x) Polynomial(1 + 2*y) Polynomial(1)
julia> [p; q; 1]
3-element Array{Polynomial{Int64,X} where X,1}:
Polynomial(1 + 2*x)
Polynomial(1 + 2*y)
Polynomial(1)
julia> G = [[p p]; [one(p) one(q)]]
2×2 Array{Polynomial{Int64,X} where X,2}:
Polynomial(1 + 2*x) Polynomial(1 + 2*x)
Polynomial(1) Polynomial(1)
julia> H = [[p p]; [q q]]
2×2 Array{Polynomial{Int64,X} where X,2}:
Polynomial(1 + 2*x) Polynomial(1 + 2*x)
Polynomial(1 + 2*y) Polynomial(1 + 2*y)
After adding
function Base.promote_eltype(p::PP, q::QQ) where {T,X,N, P<:Polynomial{T,X}, PP<:Array{P,N},
S,Y, Q<:Polynomial{S,Y}, QQ<:Array{Q,N}}
Polynomials.assert_same_variable(X,Y)
⟒(P){promote_type(T,S),X}
end
I got
julia> G = [[p p]; [one(p) one(q)]]
ERROR: ArgumentError: Polynomials have different indeterminates
Stacktrace:
[1] assert_same_variable(::Symbol, ::Symbol) at C:\Users\Andreas\.julia\packages\Polynomials\1nhQ0\src\common.jl:394
[2] promote_eltype(::Array{Polynomial{Int64,:x},2}, ::Array{Polynomial{Int64,:y},2}) at .\REPL[40]:3
[3] vcat(::Array{Polynomial{Int64,:x},2}, ::Array{Polynomial{Int64,:y},2}) at C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.5\SparseArrays\src\sparsevector.jl:1071
[4] top-level scope at REPL[55]:1
julia> H = [[p p]; [q q]]
ERROR: ArgumentError: Polynomials have different indeterminates
Stacktrace:
[1] assert_same_variable(::Symbol, ::Symbol) at C:\Users\Andreas\.julia\packages\Polynomials\1nhQ0\src\common.jl:394
[2] promote_eltype(::Array{Polynomial{Int64,:x},2}, ::Array{Polynomial{Int64,:y},2}) at .\REPL[40]:3
[3] vcat(::Array{Polynomial{Int64,:x},2}, ::Array{Polynomial{Int64,:y},2}) at C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.5\SparseArrays\src\sparsevector.jl:1071
[4] top-level scope at REPL[56]:1
which are okay.
One conversation on discourse caught my eye:
The new method:
function Base.promote_eltype(p::PP, q::QQ) where {T,X,N, P<:Polynomial{T,X}, PP<:Array{P,N},
S,Y, Q<:Polynomial{S,Y}, QQ<:Array{Q,N}}
Polynomials.assert_same_variable(X,Y)
Polynomial{promote_type(T,S),X}
end
Is essentially introducing something that is not otherwise expected for working with arrays arrays. (I also had to edit what I wrote above).
I'm not sure it matches what you want or not. (I had naively expected matrices constructed from sub-blocks to have promoted entries, but this is not the case unless a scalar is used for a 1x1 block.)
Boy this is fiddly,
You found [p q 1]
promotes, but [1 p q]
does not, throwing an error. Seems like that needs to be corrected.
Similarly with [p;q;1]
.
The last two (G = [[p p]; [one(p) one(q)]]
for example) need this this extra method to throw that error. Unfortunately, G = [[one(q) one(p)]; [p p]]
doesn't error (as the symbol for [one(q) one(p)]
is :x
Okay, getting some progress on this. Does the following work as expected on the patterns you would like to support? If so, I can generalize to other types and integrate into Polynomials.jl
. Thanks for looking at this!
# how to integrate Polynomials.jl with matrices
using Polynomials
AbstractPolynomial = Polynomials.AbstractPolynomial
using Test
using LinearAlgebra
## These are modifications that need to be added to Polynomials.jl to get
## matrices to behave as expected.
_isconstant(p::Polynomials.AbstractPolynomial) = Polynomials.isconstant(p)
_isconstant(x) = true
_constantterm(p::Polynomials.AbstractPolynomial) = Polynomials.constantterm(p)
_constantterm(x) = x
_adjust_constant(p) = _isconstant(p) ? _constantterm(p) : p
function Base.convert(::Type{S}, p::P) where {S <: Number,T, P<:Polynomials.AbstractPolynomial{T}}
Polynomials.isconstant(p) && return convert(S, Polynomials.constantterm(p))
throw(ArgumentError("Can't convert a nonconstant polynomial to type $S"))
end
Base.eltypeof(p::P) where {P <: Polynomials.AbstractPolynomial} = P
Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X},
S, Q<:AbstractPolynomial{S,X}} =
Polynomial{promote_type(T, S),X}
Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X},
S,Y, Q<:AbstractPolynomial{S,Y}} =
Polynomials.assert_same_variable(X,Y)
function Base.promote_eltypeof(p::P, qs...) where {T,X, P<: Polynomials.AbstractPolynomial{T,X}}
p′ = _adjust_constant(p)
qs′ = _adjust_constant.(qs)
Base.promote_type(Base.eltypeof(p′), Base.promote_eltypeof(qs′...))
end
function Base.promote_eltypeof(p::P, q::Q) where {T,X, P<: Polynomials.AbstractPolynomial{T,X},
S,Y, Q<: Polynomials.AbstractPolynomial{S,Y}}
_isconstant(p) || _isconstant(q) || Polynomials.assert_same_variable(X,Y)
p′ = _adjust_constant(p)
q′ = _adjust_constant(q)
Base.promote_type(Base.eltypeof(p′), Base.promote_eltypeof(q′))
end
# some testing
p = Polynomial([1,2],:x)
q = Polynomial([1,2],:y)
r = ImmutablePolynomial([1,2],:x)
s = ImmutablePolynomial([1,2],:y)
[p p]
[p r]
[p 1]
[1 1 p]
@test_throws ArgumentError [p q]
@test_throws ArgumentError [p s]
[p one(q)]
[p one(s)]
[p one(q) 1]
[1 one(q)]
[1 1 one(q)]
[1 one(q) p]
[1 1; one(q) p]
hcat([p p; 1 one(q)], I)
@test_throws ArgumentError [[p p]; [q q]]
[[p p]; [one(p) one(q)]]
Sorry not being able to test earlier, but I was busy with the transition of DescriptorSystems to v2.0
of Polynomials
. Finally, I succeded to update to a new release. The compilation times are once again longer (between 40 and 60 minutes), but this time I was not able to discover where things are jamming.
The above code is OK. But ...
I wonder if
julia> [p q I]
1×3 Array{Any,2}:
Polynomial(1 + 2*x) Polynomial(1 + 2*y) UniformScaling{Bool}(true)
should produce an error, or the following error message is the right one
julia> [p q;I]
ERROR: ArgumentError: number of columns of each array must match (got (2, 1))
Stacktrace:
[1] _typed_vcat(::Type{Any}, ::Tuple{Array{Any,2},Array{Any,2}}) at .\abstractarray.jl:1439
[2] typed_vcat(::Type{Any}, ::Array{Any,2}, ::Array{Any,2}) at .\abstractarray.jl:1453
[3] typed_hvcat(::Type{Any}, ::Tuple{Int64,Int64}, ::Polynomial{Int64,:x}, ::Vararg{Any,N} where N) at .\abstractarray.jl:1830
[4] hvcat(::Tuple{Int64,Int64}, ::Polynomial{Int64,:x}, ::Vararg{Any,N} where N) at .\abstractarray.jl:1805
[5] top-level scope at REPL[63]:1
Probably the following should work
julia> [p p;I]
ERROR: ArgumentError: number of columns of each array must match (got (2, 1))
Stacktrace:
[1] _typed_vcat(::Type{Any}, ::Tuple{Array{Any,2},Array{Any,2}}) at .\abstractarray.jl:1439
[2] typed_vcat(::Type{Any}, ::Array{Any,2}, ::Array{Any,2}) at .\abstractarray.jl:1453
[3] typed_hvcat(::Type{Any}, ::Tuple{Int64,Int64}, ::Polynomial{Int64,:x}, ::Vararg{Any,N} where N) at .\abstractarray.jl:1830
[4] hvcat(::Tuple{Int64,Int64}, ::Polynomial{Int64,:x}, ::Vararg{Any,N} where N) at .\abstractarray.jl:1805
[5] top-level scope at REPL[66]:1
Thanks!
The issue with [p q I]
comes down to Base.promote_eltypeof(q,I)
returning type Any
, which matches what happens if q=1
, say (e.g. [1 1.0 I]
is similar, having eltype Any
. I would guess that is how it should be.
Similarly, [p q;I]
is an error through the same mechanism as [1 1.0; I]
. That same error comes about with [p p; I]
. Maybe such things could be worked around by adding methods to Base.promote_eltypeof
for Arrays of Polynomials. Somewhere in this thread I did that.
But it seems that the right model for polynomials as matrix elements would be to treat non-constant polynomials as scalars of type P{T,X}
; constant polynomials as scalars of type T
, and not allow mixing of indeterminates under this view point. And that is all with the modifications to the underlying generic Julia code.
I did notice that what I had above needs even more! Basically [a,b]
calls vect
which calls Base.promote_typeof
; whereas [a b]
and [a;b]
call cat
which calls Base.promote_eltypeof
. I'd guess this is related to how blocks are treated in matrix construction. At sometime you had sent in many modifications you had made to get this all to work and I was hopeful it could be done with much less work. Now I'm a bit afraid to go back and check :) For now I have similar definitions in each, of the following type:
_adjust_constant(p::Polynomials.AbstractPolynomial) = Polynomials.isconstant(p) ? Polynomials.constantterm(p) : p
_adjust_constant(x) = x
## [a,b] calls `Base.vect` which in turn calls Base.promote_typeof for promotion
Base.promote_typeof(p::P) where {P <: Polynomials.AbstractPolynomial} = Base.eltypeof(_adjust_constant(p))
function Base.promote_typeof(p::P, xs...) where {P <: Polynomials.AbstractPolynomial}
x = _adjust_constant(p)
Base.promote_type(Base.typeof(x), Base.promote_typeof(xs...))
end
function Base.promote_typeof(p::P, q::Q) where {T,X, P<: Polynomials.AbstractPolynomial{T,X},
S,Y, Q<: Polynomials.AbstractPolynomial{S,Y}}
isconstant(p) || isconstant(q) || Polynomials.assert_same_variable(X,Y)
p′ = _adjust_constant(p)
q′ = _adjust_constant(q)
Base.promote_type(Base.eltypeof(p′), Base.eltypeof(q′))
end
## [p q] and [p;q] call `Base.cat` which in turn call Base.promote_eltypeof for promtion
Base.eltypeof(p::P) where {P <: Polynomials.AbstractPolynomial} = P
function Base.promote_eltypeof(p::P, xs...) where {T,X, P<: Polynomials.AbstractPolynomial{T,X}}
x = _adjust_constant(p)
Base.promote_type(Base.eltypeof(x), Base.promote_eltypeof(xs...))
end
function Base.promote_eltypeof(p::P, q::Q) where {T,X, P<: Polynomials.AbstractPolynomial{T,X},
S,Y, Q<: Polynomials.AbstractPolynomial{S,Y}}
isconstant(p) || isconstant(q) || Polynomials.assert_same_variable(X,Y)
p′ = _adjust_constant(p)
q′ = _adjust_constant(q)
Base.promote_type(Base.eltypeof(p′), Base.eltypeof(q′))
end
I'm guessing, but just looked at DescriptorSystems
. In RationalFunction.jl
, the struct:
struct RationalTransferFunction{T} <: AbstractRationalTransferFunction
num::Polynomial{T} # numerator polynomial
den::Polynomial{T} # denominator polynomial
...
Should include the X, as in
struct RationalTransferFunction{T,X} <: AbstractRationalTransferFunction
num::Polynomial{T,X} # numerator polynomial
den::Polynomial{T,X}
...
Unfortunately, this won't work with Polynomials v1.2; so if you were supporting both; you would want to define this conditionally on the version of Polynomials.
Thanks. I will make the changes (I expect a small chain reaction). Still I wonder why the present version works, even with the "truncated" type. Do you have perhaps an answer?
john verzani @.***> schrieb am Fr., 19. März 2021, 16:24:
I'm guessing, but just looked at DescriptorSystems. In RationalFunction.jl, the struct:
struct RationalTransferFunction{T} <: AbstractRationalTransferFunction num::Polynomial{T} # numerator polynomial den::Polynomial{T} # denominator polynomial ...
Should include the X, as in
struct RationalTransferFunction{T,X} <: AbstractRationalTransferFunction num::Polynomial{T,X} # numerator polynomial den::Polynomial{T,X} ...
Unfortunately, this won't work with Polynomials v1.2; so if you were supporting both; you would want to define this conditionally on the version of Polynomials.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JuliaMath/Polynomials.jl/issues/312#issuecomment-802913167, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALJDHECSBC2GADPJJ2ZOK5DTENUETANCNFSM4ZCNVYTQ .
It isn't wrong, it just isn't as performant as having a fully realized type in the field of the struct. (If you don't add that X, you don't have a concrete type:
julia> Base.isconcretetype(Polynomial{Float64,X} where {X})
false
julia> Base.isconcretetype(Polynomial{Float64,:x})
true
) My guess is the slowdown occurs here. (But it is only a guess.)
Sorry, want to pick your brain, as you have more developed ideas about what is expected than I do.
I have some promotion rules for mixing polynomials with constant polynomials and scalars that I think express this:
This is kinda working. Things like [1 p]
promote to [Polynomial(1) p]
, for example when p
is non-constant. But they have this side effect [1 one(p)]
promotes to [1 1]
, which kind of makes sense, but I have a choice as to what becomes of [one(p)]
. WIll it be [Polynomial(1)]
or [1]
? I could see either are being considered correct.
Regarding the last issues, I obtained
julia> [1 one(Polynomial)]
1×2 Array{Polynomial{Float64,:x},2}:
Polynomial(1.0) Polynomial(1.0)
which is correct (in my opinion) (see also [1 one(im)]
).
And also
julia> [one(Polynomial)]
1-element Array{Polynomial{Float64,:x},1}:
Polynomial(1.0)
which is what I expected (compare with[one(1+im)]
).
In #328 I approach this problem a different way. I've been mumbling about what to do here for awhile, and haven't been really happy with what I had sketched out in #325 as it was too complicated and non-performant. In #328 a more explicit approach is required for mixing of variables. It may seem more cumbersome, but eliminates the question of what should [one(Polynomial,:x) one(Polynomial,:y)]
be.
Would that be suitable for your needs?
Sorry, I lost the point. I hope you will take the right decision.
john verzani @.***> schrieb am Mo., 29. März 2021, 21:49:
In #328 https://github.com/JuliaMath/Polynomials.jl/pull/328 I approach this problem a different way. I've been mumbling about what to do here for awhile, and haven't been really happy with what I had sketched out in #325 https://github.com/JuliaMath/Polynomials.jl/pull/325 as it was too complicated and non-performant. In #328 https://github.com/JuliaMath/Polynomials.jl/pull/328 a more explicit approach is required for mixing of variables. It may seem more cumbersome, but eliminates the question of what should [one(Polynomial,:x) one(Polynomial,:y)] be.
Would that be suitable for your needs?
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JuliaMath/Polynomials.jl/issues/312#issuecomment-809665011, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALJDHEDAJ4DJZKF4CUNT6TTTGDKSXANCNFSM4ZCNVYTQ .
Sorry, the basic point is for performance reasons the solution I want to merge requires the user to be specific about the element type when mixing terms with different variables through the use of a typed constructor.
So if p=Polynomial([1,2],:x)
and q = Polynomial(3, :y)
(a constant) we would have
[p,q]
would throw an error (as :x is not :y)
Polynomial{Int,:x}[p,q]
would be allowed, with q
promoting to the specified type (as a constant polynomial can be converted to a polynomial with a different indeterminate).
Of course, in a function you would likely use typeof(p)[p, q]
or some variant to get the type of p
.
The use of numbers, which slowed your code down, would not need such annotations (e.g. [1,p] would just work).
Thanks for the explanations. It's OK for me. I guess for rational functions the same approach will be used.
john verzani @.***> schrieb am Mo., 29. März 2021, 23:55:
Sorry, the basic point is for performance reasons the solution I want to merge requires the user to be specific about the element type when mixing terms with different variables through the use of a typed constructor.
So if p=Polynomial([1,2],:x) and q = Polynomial(3, :y) (a constant) we would have
[p,q] would throw an error (as :x is not :y)
Polynomial{Int,:x}[p,q] would be allowed, with q promoting to the specified type (as a constant polynomial can be converted to a polynomial with a different indeterminate).
Of course, in a function you would likely use typeof(p)[p, q] or some variant to get the type of p.
The use of numbers, which slowed your code down, would not need such annotations (e.g. [1,p] would just work).
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JuliaMath/Polynomials.jl/issues/312#issuecomment-809739540, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALJDHEEZBUFQCT2OQIQN6HDTGDZM3ANCNFSM4ZCNVYTQ .
Closed with #328
In the current version v2.0 the mixing of variables is allowed in the concatenation operations:
Is this a feature or a bug?
I hope very much this is a bug!