-
-
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
Generic abstract array construction without instances #25107
Comments
Linking #18161. Best! |
I'm not convinced a special type is necessary here: I'm pretty happy with BandedMatrix(uninitialized, (n,m), (l,u)) which could be generalized with a simple routine arrayparameters(A::Array) = size(A) # or maybe (size(A),)
arrayparameters(A::BandedMatrix) = (size(A), bandwidths(A)) then general code would work via function foo(A:: AbstractArray)
# make empty array of type typeof(A) with correct parameters:
ret = typeof(A)(uninitialized, arrayparameters(A)...)
# ... do stuff
end |
Yes, |
I don't think the arrayparameters(A::ArrayOfArrays{<:AbstractArray}) = (arrayparameters.(A),)
function ArrayOfArrays{T}(::Uninitialized, pars) where T <: AbstractArray
ret = Array{T}(uninitialized, size(pars))
for (k,j) in eachindex(ret)
ret[k,j] = T(uninitialized, pars[k,j]...)
end
ArrayOfArrays(ret)
end |
Here's another way to describe it @dlfivefifty. Let's say I allow the user to tell me what type of matrix the Jacobian will be, and I will build a few caches for that. If I want the user to be able to pass me construction information like The other way of course is to require that the user just passes Instead of phrasing it as whether a special type is necessary, I think the better question is: why should we have to special case for every possible sparsity structure instead of just formalizing an API for sparsity structures? |
Is there an example not of the form |
Well everything can always be a type and a list of parameters otherwise you couldn't construct it in the first place, but that gets painful. For example, what about a non-binary tree abstract array, like MultiScaleArrays.jl? That could easily be a huge type that's hard to construct, so I don't see why we should be constructing a false tree to be able to While you can keep on passing on tuples of unknown structure information, one thing that begins to happen is you cannot query this information until the type is actually created. For example, if we have a function where we get the sparsity structure from the user function f(x,y;pars=nothing,pars_type = nothing)
# do some stuff
J = pars_type(pars)
end
# User code
pars=(...) # all of the stuff for a SparseMatrixCSC
f(x,y,pars=(...),pars_type=SparseMatrixCSC) One thing to note is that you cannot get the size of the matrix they want to construct until after you construct it, unless you special case for every |
OK, MultiScaleArrays.jl is a good example (where you can use a type to hide the types of the parameters, unlike But you could just still fit in the above framework: arrayparameters(A::MultiScaleArray) = (MultiScaleArrayInformation(A),) thus avoiding the need for lots of extra types for Base arrays. |
But that still requires having the constructed array in order to call |
Where do you get the Array parameters then?
In the banded matrix case, I can only imagine two scenarios:
1. Generic code where you have a matrix but don’t know it’s type
2. You know you want a banded matrix so you specify the bandwidths.
I cannot imagine a scenario where you have generic code but need to will into existence an array with specific parameters, because where do those parameters come from?
…Sent from my iPhone
On 8 Mar 2018, at 15:16, Christopher Rackauckas ***@***.***> wrote:
But that still requires having the constructed array in order to call arrayparameters, which is exactly the thing I would like to try and avoid.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
The Jacobian or the |
Right, so you are given the necessary information as an argument to the function. I don't see why there needs to be extra types in Base to do this, as you can always take the type and the parameters as two arguments (or a 2-tuple): function construct(::Type{AT}, pars) where AT<:AbstractArray =
AT(uninitialized, pars...) will do exactly why you want, you just need to give it two arguments instead of one. |
But going back to one of the first questions, how does that let me query details of the future matrix, like its size, directly from pars? Do I need to write |
It's nothing that can't be worked around: The issue is that adding a special type in Base just adds more code and more complexity...and it seems unlikely to happen in v1.0 given the tight timetable. |
I don't get why we should settle for a method that only works on some arrays (and works without a defined interface). And I never asked for 1.0. This issue becomes pretty apparent when writing code for a generic matrix input (and only generic, so most Base arrays are fine), so I assumed I'd have to wait until there are more people running into this problem for it to pick up steam. FWIW the proposal for Jacobian types in DiffEq right now uses the prototype approach in order to get around this for now (SciML/DifferentialEquations.jl#220) |
I've changed my mind now that BandedMatrices.jl supports general backends, and how hard it is to create general matrices. I think @ChrisRackauckas suggestion is a good one. |
We do not have a good way of fully specifying abstract arrays without giving the full instance. For example, in DiffEq I would like the user to be able to pass whatever kind of Jacobian matrix type suits their needs. This would allow them to specify a banded matrix from BandedMatrices.jl, or a sparse matrix, etc. However, this is going to be part of the high level problem type which is something that I assume holds little memory and thus is simple to copy around (since it is in every other case). I would like to not hold instances of giant sparse matrices here.
The problem is that there is no way to re-construct that sparse matrix generically only from type information. If I know the instance, I can do:
similar(A)
to get a new cache of it, or I can do
similar(A,T)
to get a new cache with a different element type (which can be necessary for handling units). But, if I just have the type, I don't know how to re-create their sprasity pattern, and thus I am stuck with the problem type holding instances of these large objects in a very memory inefficient manner.
Now, the way this is supposed to work is that someone in this case uses the
sparse
constructor along with some array which specifies the non-zero elements. And for dense arrays one can pass a tuple for size. And for BandedMatrix ...? You can see that handling this using the given methods is not generic: I would have to setup each cache construction to handle every specific case the user can throw at me.But it doesn't need to be like this. If instead we had an idea of "the abstract information which is required to re-build this type", then we could specify the way to construct any abstract type without providing instances. The simplest case would be dense arrays, where
and thus (I am not wedded to these names at all)
is sufficient. But for sparse arrays, this could be different:
where
nonzeros
can be any sufficiently nice collection (so this could be specified with a lazy array if this pattern is simple enough, making it take almost zero memory) and thusconstruct
can be used on this. But then users can extend this. For example, the BandedMatrices.jl package could define the constructor informationAnd then this can be used with
construct
. EachAbstractArray
can have a much more memory effect "layout" information object from which it can be constructed, and thus this makes it easier to pass around ideas aboutAbstractArray
s without passing and re-creating instances themselves.The text was updated successfully, but these errors were encountered: