-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
RFC/WIP: Support array type as input for functions returning AbstractArray instance #16740
Conversation
We might need to distinguish between subtypes of |
@andreasnoack You're right, of course. I meant to exclude these view types (and did for |
What is fixate supposed to mean? Sparse matrices with Any eltype are useful once in a while, it would be nice not to lose the concise way of initializing them. Why does spzeros have to call zero on the eltype? |
The term fixate is surely questionable. I'm trying to say "if there is no eltype yet, use the given default". The generic |
|
@@ -17,12 +17,16 @@ immutable SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti} | |||
n < 0 && throw(ArgumentError("number of columns (n) must be ≥ 0, got $n")) | |||
new(Int(m), Int(n), colptr, rowval, nzval) | |||
end | |||
SparseMatrixCSC(m::Integer, n::Integer) = SparseMatrixCSC{Tv,Ti}(m, n, ones(Ti, max(n,0)+1), Array{Ti}(0), Array{Tv}(0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably check for negative n instead of allowing n not to match length(colptr)-1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That duplicates the check from the constructor above which it delegates to, but is probably better than the current code, I agree. Maybe even better to have a dedicated function to check all sizes are non-negative, as this check should occur in all the array constructors at some point.
Anyone got an idea about the segfault on travis? |
d8973f3
to
e51bb90
Compare
Updated with the feedback I got so far (except for #16740 (comment), |
speye(m::Integer, n::Integer) = speye(Float64, m, n) | ||
speye(n::Integer) = eye(SparseMatrixCSC, n) | ||
speye(T::Type, n::Integer) = eye(SparseMatrixCSC{T}, n, n) | ||
speye(m::Integer, n::Integer) = eye(SparseMatrixCSC, m, n) | ||
|
||
""" | ||
speye(S) | ||
|
||
Create a sparse identity matrix with the same structure as that of `S`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems wrong - it's same size, not same nonzero structure, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Same nonzero structure wouldn't make much sense anyway. Good opportunity to correct that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with explicit zeros I guess it could, but I think Y=copy(S); fill!(Y.nzval, 0); Y
is fine for that at the moment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are thinking of spzeros
now, aren't you? That doesn't support the spzeros(S)
signature. (zeros(S)
works, but doesn't preserve to nonzero structure, either).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes of course, don't review without coffee
e51bb90
to
848234f
Compare
Updated again, incorporating remaining feedback. New functionality is now having a fill(SparseMatrixCSC, 2, (3,3)) # gives a SparseMatrixCSC{Int64,Int64}
fill(SparseMatrixCSC{Float64}, 2, (3,3)) # gives a SparseMatrixCSC{Float64,Int64} For this to work I need the I've also discovered an inconsistency of julia> fill(Diagonal, 1, (3,3))
3×3 Diagonal{Int64}:
1 ⋅ ⋅
⋅ 1 ⋅
⋅ ⋅ 1
julia> fill(Bidiagonal, 1, (3,3))
ERROR: ArgumentError: Array A of type Bidiagonal{Int64} and size (3,3) can
not be filled with x=1, since some of its entries are constrained.
in fill!(::Bidiagonal{Int64}, ::Int64) at ./linalg/bidiag.jl:515
in fill(::Type{Bidiagonal}, ::Int64, ::Tuple{Int64,Int64}) at ./abstractarray.jl:382
in eval(::Module, ::Any) at ./boot.jl:225
in macro expansion at ./REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46
julia> fill(Tridiagonal, 1, (3,3))
ERROR: ArgumentError: Array A of type Tridiagonal{Int64} and size (3,3) can
not be filled with x=1, since some of its entries are constrained.
in fill!(::Tridiagonal{Int64}, ::Int64) at ./linalg/bidiag.jl:515
in fill(::Type{Tridiagonal}, ::Int64, ::Tuple{Int64,Int64}) at ./abstractarray.jl:382
in eval(::Module, ::Any) at ./boot.jl:225
in macro expansion at ./REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46 I'd say both behaviors may be desirable at times, I have no clear favorite, but I'd like this to be consistent. Are the "historical reasons" still relevant? Maybe export |
I've been meaning to report and/or fix that exact issue, essentially that |
Hmmm, segfault in |
848234f
to
b00c23d
Compare
b00c23d
to
5e50456
Compare
ce2e659
to
70635a7
Compare
Next bit is in place: Array[speye(10,10) ones(10)]
SparseMatrixCSC[speye(10,10) ones(10)] return a 10×11 matrix of type SparseVector[spzeros(10), ones(10)] returns a EDIT: Looks like I messed up the commits I have pushed somewhere and trying to fix it in a hurry didn't work (as usual), so it's a bit early for code review, but please share your thoughts about the general idea. |
a9c8ee0
to
2d76236
Compare
992f285
to
4b1aaa7
Compare
Except for everything |
Ensures all sizes are non-negative, throws ArgumentError otherwise
Checks both given sizes are equal, throws error if not
Support the following: BitArray{N}(::Dims{N}) Diagonal(::Integer, ::Integer) Diagonal(::Dims{2}) Diagonal{T}(::Integer, Integer) Diagonal{T}(::Dims{2})
Support the following: SparseMatrixCSC(::Dims{2}) SparseMatrixCSC(::Integer, ::Integer) SparseMatrixCSC{Tv}(::Dims{2}) SparseMatrixCSC{Tv}(::Integer, ::Integer) SparseMatrixCSC{Tv,Ti}(::Dims{2}) SparseMatrixCSC{Tv,Ti}(::Integer, ::Integer) SparseVector(::Dims{1}) SparseVector(::Integer) SparseVector{Tv}(::Dims{1}) SparseVector{Tv}(::Integer) SparseVector{Tv,Ti}(::Dims{1}) SparseVector{Tv,Ti}(::Integer)
SymTridiagonal For Type in Bidiagonal, Tridiagonal, SymTridiagonal, support the following: Type(::Dims{2}) Type(::Integer, ::Integer) Type{T}(::Dims{2}) Type{T}(::Integer, ::Integer)
desired array type as first argument
And make hcat/vcat/hvcat returning sparse matrices just call the corresponding typed_*cat function.
And make hcat/vcat involving SparseVectors just call the corresponding typed_*cat function.
4b1aaa7
to
e7e9bb1
Compare
else | ||
return T | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love the idea. Maybe we can do it even more general? E.g.
function set_eltype{A<:AbstractArray}(::Type{A}, T::Type)
Base.@_pure_meta
AA = A
AT = AA.name.primary
while AT.name.primary != AbstractArray
AA = supertype(AA)
AT = supertype(AT)
end
if AA.parameters[1] !== T
tvarname = AT.parameters[1].name
params = A.parameters
new_params = collect(A.name.primary.parameters)
for i in eachindex(new_params)
p = new_params[i]
if isa(p, TypeVar) && p.name == tvarname
new_params[i] = T
else
new_params[i] = params[i]
end
end
return A.name.primary{new_params...}
else
return A
end
end
so it also gives set_type(Diagonal{Int}, Float64) === Diagonal{Float64})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I deliberately wanted fixate_eltype(Diagonal{Int}, Float64) === Diagonal{Int}
to be able to specify a default element type. E.g. zeros(Diagonal, 2, 2)
should give a Diagonal{Float64}
, but zeros(Diagonal{Int}, 2, 2)
should give a Diagonal{Int}
. Maybe a third, boolean argument could be used to choose between "set always" and "set only if unset".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I had in mind another use case, using something like this combined with something like deepcopy_internal
to write a generic similar(::AbstractArray, ::Type)
. Handling the dimensions in a generic fashion might not be possible and we would still need specialization (as you have here).
With this in mind and what is needed here, a third boolean argument would seem like the way to cover both scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is fundamentally invalid. T.name.primary
isn't a meaningful type and its type parameters don't have any fixed relation to the type parameters of AbtractArray
. If you can't express a type relationship via dispatch, that is highly indicative that the relation you want is not valid.
I think you want to call the similar
function, whose definition is similar to what it appears the function above was attempting to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it could be something like
function set_eltype{A<:AbstractArray}(::Type{A}, T::Type, force=false)
Base.@_pure_meta
SA = A
AA = A.name.primary
while SA.name.primary != AbstractArray
SA = supertype(SA)
force && (AA = supertype(AA))
end
if (!force && !isa(SA.parameters[1], TypeVar)) ||
(force && SA.parameters[1] === T)
return A
end
varname = (force ? AA : SA).parameters[1].name
params = A.parameters
new_params = collect(force ? A.name.primary.parameters : params)
for i in eachindex(new_params)
p = new_params[i]
if isa(p, TypeVar) && p.name == varname
new_params[i] = T
elseif force
new_params[i] = params[i]
end
end
return A.name.primary{new_params...}
end
This would give set_eltype(Diagonal, Float64) === Diagonal{Float64}
, set_eltype(Diagonal{Int}, Float64) === Diagonal{Int}
, set_eltype(Diagonal{Int}, Float64, true) === Diagonal{Float64}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've often wished that similar(::Type, ...)
returned a type rather than an instance, and that AbstractArray
subtypes implemented only it. Then a generic fallback would automatically return instances when passed instances. Wouldn't that be all we need here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing these insights, @vtjnash, it certainly is a valuable contribution to the manual. But I still don't see why the fixate_eltype
from this PR would be invalid. As said, it seems to work as I want it:
julia> fixate_eltype(Array, Float16)
Array{Float16,N}
julia> fixate_eltype(Array{Int}, Float16)
Array{Int64,N}
julia> fixate_eltype(Vector, Float16)
Array{Float16,1}
julia> fixate_eltype(Vector{Int}, Float16)
Array{Int64,1}
julia> fixate_eltype(BitArray, Float16)
BitArray{N}
julia> fixate_eltype(BitVector, Float16)
BitArray{1}
julia> type FunkyArray{NumDims, Foo, ElType, ElNiño} <: AbstractArray{ElType, NumDims}; end
julia> fixate_eltype(FunkyArray, Float16)
FunkyArray{NumDims,Foo,Float16,ElNiño}
julia> fixate_eltype(FunkyArray{42}, Float16)
FunkyArray{42,Foo,Float16,ElNiño}
julia> fixate_eltype(FunkyArray{42, :bar}, Float16)
FunkyArray{42,:bar,Float16,ElNiño}
julia> fixate_eltype(FunkyArray{42, :bar, String}, Float16)
FunkyArray{42,:bar,String,ElNiño}
julia> fixate_eltype(FunkyArray{42, :bar, String, -1}, Float16)
FunkyArray{42,:bar,String,-1}
There is one corner case I could find:
julia> type UntypedArray <: AbstractArray; end
julia> fixate_eltype(UntypedArray, Float16)
UntypedArray
I'm not sure what would be desired in this case, though. Maybe throwing instead of returning a type with still unspecified element type would be better. Anyway, this kind of type does not look exactly useful, so I'm not worried too much.
T.name.primary
isn't a meaningful type and its type parameters don't have any fixed relation to the type parameters ofAbtractArray
.
That sounds indeed worrying for the proposed fixate_eltype
. However, T.name.primary
is used in a couple of places around base, so there has to be something of value in it. Also, the relationship of the type parameters between, say, Array
and AbstractArray
has be to recorded somewhere. Why shouldn't we be able to exploit it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vtjnash Comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the conclusion is that this should be implemented with dispatch cf. the new paragraphs in the documentation and that T.name.primary
should generally be avoided. The places in Base where T.name.primary
are few and special.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH https://github.com/JuliaLang/julia/blob/7f230adcf13d7a2bcb76e55327f211b7b4f25046/doc/devdocs/types.rst#typenames reads as though the use of T.name.primary
here is perfectly valid. (Maybe the relationship of the TypeVar
s is a real problem?) It feels a bit unsatisfying to require a method to be implemented for every array type that should support e.g. zeros(MyArrayType, (5, 3))
when there is this generic alternative available.
bump, any plan to revisit? |
Not any time soon (for lack of time, I'm afraid), so if someone wants to take over, go ahead! Otherwise, I might come back to this eventually. |
This is mostly superseded. This main thing in this PR that might still be relevant is the change to the concatenation functions, but I'm not sure how much they were liked anyway. |
This is a shot at #11557.
At the moment, only
zeros
,ones
, andeye
are supported. For examplewill return 4×4 identity matrices of the indicated types. (EDIT: Changed example to use
Bidiagonal
.) There is still some work ahead, but I'd appreciate feedback on the approach taken before committing more time to this. To avoid code duplication, I have addedType(dims...)
like constructors for all array types where it makes sense and then implementedzeros
,ones
, andeye
for{T<:AbstractArray}(::Type{T}, dims...)
using these.To exercise things,
spzeros
now simply falls back tozeros
whilespeye
forwards to a customeye(::Type{SparseMatrixCSC}, dims...)
(which is more efficient than the generic implementation). So far, no syntax or functionality is invalidated/removed, with one exception:spzeros(Any, ...)
now fails withERROR: MethodError: no method matching zero(::Type{Any})
, while previously it succeeded in constructing a matrix, which however was of rather limited use.In addition to extending this to more functions (suggestions welcome) and adding tests and docs, there is a more conceptual aspect that might need some work: the way missing type parameters are handled. Presently, all new constructors just use
Float64
as default eltype which just happens to be the right thing to do for these functions, but seems a bit arbitrary and requires special-casingArray
, which defaults toArray{Any}
. I'm thinking about something likefixate_eltype{T<:AbstractArray}(::Type{T}, default::Type)
that would be implemented by array types like e.g.Thoughts?