Enable writing fields with Vecs of unknown length
Like VTK, VTK-m has an explicit way to represent vectors/tuples in an array
to express things such as 3D coordinates. Although the memory layout is
(typically) the same between the two, there is a subtle but important
limitation in the way VTK-m represents them.
In VTK, the size of a "tuple" is selected at runtime. This allows the size
of the tuples to easily be adjusted to whatever is needed. When using an
array with a tuple size determined at runtime, small arrays are allocated
to move data in and out of the array.
However, this approach does not work well on a GPU because dynamically
allocating memory of the appropriate size is problematic. Instead, VTK-m
takes a slightly different approach. VTK-m defines a simple templated type
named `Vec` that represents a short vector of components with a length
defined at compile time. Because the length is defined at compile time,
operating on arrays of indeterminate type is problematic. Operating
directly on the data requires a separate compilation path for each vector
length.
When the size of the `Vec` in a VTK-m array is not specifiable, then
creating all code paths for any possible array type is problematic. Thus,
VTK-m needs ways to operate on these arrays.
Fortunately, we have made some recent strides on making this possible for a
variety of use cases, but some problems still exist. This issue will start
with listing the features that are currently available and then list those
that remain.
## Features that are available
Several recent additions allow VTK-m algorithms to operate on arrays with
arbitrary `Vec` lengths. The most important feature is the
`ArrayExtractComponent` function that allows pulling a single component of
the array out without copying data and without knowing the length of the
`Vec`s. This is used for multiple methods of `UnknownArrayHandle` to
extract arrays of known type regardless of what kind of array is stored.
It is also possible to create new arrays that have the same vector size as
an input array. The `NewInstance`, `NewInstanceBasic` and
`NewInstanceFloatBasic` methods of `UnknownArrayHandle` all provide a means
of creating a new array with an unknown vector size although you need an
existing array with the target vector size already.
It is also usually straightforward to create a scalar array with the same
component type as that of an input array of `Vec`s. This is usually done
after some sort of `CastAndCall` determines the component type of the
input.
## What is missing
There are some features that are still difficult or impossible with the
current features.
### Get extracted array with float fallback
As mentioned, `UnknownArrayHandle` has a way of extracting a component from
an array knowing only the base component type of the array. This can be
used to get an "extracted array," and this in turn is used with a special
`CastAndCall` to get arrays of any storage or `Vec` length.
However, this `CastAndCall` does not have a means for a float fallback.
This is less important than other `CastAndCall`s as there are only about 10
possibilities. However, for complex operations it might be better to just
worry about a restricted set of types. Thus, it would be helpful to have a
`CastAndCallWithExtractedArrayWithFloatFallback`. This mouthful of a method
would allow someone to provide a `vtkm::List` of component types to try,
and would convert to `vtkm::FloatDefault` if none of these worked.
### Construct an array of arbitrary `Vec` length
Currently, to create an array of `Vec`s, you either need to know the size
of the `Vec`s at compile time or have an array of the same `Vec` size you
want. This can be problematic in some instances. For example, if a filter
were to create a field that had `Vec`s with twice as many components as the
input (say, computing a confidence interval), that could not be done in a
single array. This also comes up a lot when importing data from other
sources such as when reading data from a file or converting from VTK.
The proposed solution would be to create a new type of `ArrayHandle` that
is a cross between `ArrayHandleGroupVec` and `ArrayHandleGroupVecVariable`.
It would essentially behave like `ArrayHandleGroupVecVariable` in that the
size of the `Vec`s could be assigned at runtime, but it would limit the
array to having the same `Vec` size for every entry. In this respect it is
like `ArrayHandleGroupVec` but would not have to set the size at compile
time. We could call this class `ArrayHandleGroupVecRuntime` (unless a
better name comes along).
We won't dwell on the implementation specifics of
`ArrayHandleGroupVecRuntime`. The implementation will be very similar to
`ArrayHandleGroupVecVariable` except that the offsets can be implied
implicitly.
Of course, simply creating an `ArrayHandleGroupVecRuntime` does not really
solve the problem. Sure, you could create this array and put it in a field,
but we don't want to add yet another type of storage to compile into all
the algorithms in VTK-m. However, we can get around this problem by doing
some implicit conversions in `UnknownArrayHandle`.
`UnknownArrayHandle` already provides some implicit conversions when data
does not need to be copied. For example, if you ask an `UnknownArrayHandle`
for an `ArrayHandleMultiplexer`, then it will return the array if it
matches any of the conditions of that array.
In the same way, we can create special checks for a basic `ArrayHandle`.
When requesting an `ArrayHandleBasic`, `UnknownArrayHandle` can check if it
contains an `ArrayHandleGroupVecRuntime` of the appropriate type and
length. It should then be possible to share `Buffer` objects to transform
the data to the expected type.
Although a bit more tricky, we should be able to do the opposite as well.
That is, ask for an `ArrayHandleGroupVecRuntime` and have it match any
`ArrayHandleBasic` of the appropriate component type.
issue