Update the developing algorithms section of the user's guide

This commit is contained in:
Kenneth Moreland 2023-10-27 15:23:29 +11:00
parent 92e7425b8d
commit cba1068bec
24 changed files with 1883 additions and 84 deletions

@ -102,7 +102,7 @@ def exlineref_role(name, rawtext, text, lineno, inliner, options = {}, content =
# Strip optional `ex:` prefix.
match = re.fullmatch(r'ex:(.*)', examplename)
if match:
examplename = group(1)
examplename = match.group(1)
try:
example = find_examples.get_example(examplename)

@ -0,0 +1,202 @@
==============================
Basic Array Handles
==============================
.. index:: array handle
:chapref:`dataset:Data Sets` describes the basic data sets used by |VTKm|.
This chapter dives deeper into how |VTKm| represents data.
Ultimately, data structures like \vtkmcont{DataSet} can be broken down into arrays of numbers.
Arrays in |VTKm| are managed by a unit called an *array handle*.
An array handle, which is implemented with the :class:`vtkm::cont::ArrayHandle` class, manages an array of data that can be accessed or manipulated by |VTKm| algorithms.
It is typical to construct an array handle in the control environment to pass data to an algorithm running in the execution environment.
It is also typical for an algorithm running in the execution environment to populate an array handle, which can then be read back in the control environment.
It is also possible for an array handle to manage data created by one |VTKm| algorithm and passed to another, remaining in the execution environment the whole time and never copied to the control environment.
.. didyouknow::
The array handle may have multiple copies of the array, one for the control environment and one for each device.
However, depending on the device and how the array is being used, the array handle will only have one copy when possible.
Copies between the environments are implicit and lazy.
They are copied only when an operation needs data in an environment where the data are not.
:class:`vtkm::cont::ArrayHandle` behaves like a shared smart pointer in that when the C++ object is copied, each copy holds a reference to the same array.
These copies are reference counted so that when all copies of the :class:`vtkm::cont::ArrayHandle` are destroyed, any allocated memory is released.
.. doxygenclass:: vtkm::cont::ArrayHandle
:members:
------------------------------
Creating Array Handles
------------------------------
:class:`vtkm::cont::ArrayHandle` is templated on the type of values being stored in the array.
There are multiple ways to create and populate an array handle.
The default :class:`vtkm::cont::ArrayHandle` constructor will create an empty array with nothing allocated in either the control or execution environment.
This is convenient for creating arrays used as the output for algorithms.
.. load-example:: CreateArrayHandle
:file: GuideExampleArrayHandle.cxx
:caption: Creating an :class:`vtkm::cont::ArrayHandle` for output data.
Chapter \ref{chap:AccessingAllocatingArrays} describes in detail how to allocate memory and access data in an :class:`vtkm::cont::ArrayHandle`.
However, you can use the :func:`vtkm::cont::make_ArrayHandle` function for a simplified way to create an :class:`vtkm::cont::ArrayHandle` with data.
:func:`vtkm::cont::make_ArrayHandle` has many forms.
An easy form to use takes an initializer list and creates a basic :class:`vtkm::cont::ArrayHandle` with it.
This allows you to create a short :class:`vtkm::cont::ArrayHandle` from literals.
.. doxygenfunction:: vtkm::cont::make_ArrayHandle(std::initializer_list<T>&&)
.. load-example:: ArrayHandleFromInitializerList
:file: GuideExampleArrayHandle.cxx
:caption: Creating an :class:`vtkm::cont::ArrayHandle` from initially specified values.
One problem with creating an array from an initializer list like this is that it can be tricky to specify the exact value type of the :class:`vtkm::cont::ArrayHandle`.
The value type of the :class:`vtkm::cont::ArrayHandle` will be the same types as the literals in the initializer list, but that might not match the type you actually need.
This is particularly true for types like :type:`vtkm::Id` and :type:`vtkm::FloatDefault`, which can change depending on compile options.
To specify the exact value type to use, give that type as a template argument to the :func:`vtkm::cont::make_ArrayHandle` function.
.. load-example:: ArrayHandleFromInitializerListTyped
:file: GuideExampleArrayHandle.cxx
:caption: Creating a typed :class:`vtkm::cont::ArrayHandle` from initially specified values.
Constructing an :class:`vtkm::cont::ArrayHandle` that points to a provided C array is also straightforward.
To do this, call :func:`vtkm::cont::make_ArrayHandle` with the array pointer, the number of values in the C array, and a :enum:`vtkm::CopyFlag`.
This last argument can be either :enumerator:`vtkm::CopyFlag::On` to copy the array or :enumerator:`vtkm::CopyFlag::Off` to share the provided buffer.
.. doxygenfunction:: vtkm::cont::make_ArrayHandle(const T*, vtkm::Id, vtkm::CopyFlag)
.. doxygenenum:: vtkm::CopyFlag
.. load-example:: ArrayHandleFromCArray
:file: GuideExampleArrayHandle.cxx
:caption: Creating an :class:`vtkm::cont::ArrayHandle` that points to a provided C array.
.. index:: vector
.. index:: std::vector
Likewise, you can use :func:`vtkm::cont::make_ArrayHandle` to transfer data from a ``std::vector`` to an :class:`vtkm::cont::ArrayHandle`.
This form of :func:`vtkm::cont::make_ArrayHandle` takes the ``std::vector`` as the first argument and a :enum:`vtkm::CopyFlag` as the second argument.
.. doxygenfunction:: vtkm::cont::make_ArrayHandle(const std::vector<T,Allocator>&, vtkm::CopyFlag)
.. load-example:: ArrayHandleFromVector
:file: GuideExampleArrayHandle.cxx
:caption: Creating an :class:`vtkm::cont::ArrayHandle` that points to a provided ``std::vector``.
As hinted at earlier, it is possible to send :enumerator:`vtkm::CopyFlag::On` to :func:`vtkm::cont::make_ArrayHandle` to wrap an :class:`vtkm::cont::ArrayHandle` around an existing C array or ``std::vector``.
Doing so allows you to send the data to the :class:`vtkm::cont::ArrayHandle` without copying it.
It also provides a mechanism for |VTKm| to write directly into your array.
However, *be aware* that if you change or delete the data provided, the internal state of :class:`vtkm::cont::ArrayHandle` becomes invalid and undefined behavior can ensue.
A common manifestation of this error happens when a ``std::vector`` goes out of scope.
This subtle interaction will cause the :class:`vtkm::cont::ArrayHandle` to point to an unallocated portion of the memory heap.
The following example provides an erroneous use of :class:`vtkm::cont::ArrayHandle` and some ways to fix it.
.. load-example:: ArrayOutOfScope
:file: GuideExampleArrayHandle.cxx
:caption: Invalidating an :class:`vtkm::cont::ArrayHandle` by letting the source ``std::vector`` leave scope.
An easy way around the problem of having an :class:`vtkm::cont::ArrayHandle`'s data going out of scope is to copy the data into the :class:`vtkm::cont::ArrayHandle`.
Simply make the :enum:`vtkm::CopyFlag` argument be :enumerator:`vtkm::CopyFlag::On` to copy the data.
This solution is shown in :exlineref:`ex:ArrayOutOfScope:CopyFlagOn`.
What if you have a ``std::vector`` that you want to pass to an :class:`vtkm::cont::ArrayHandle` and then want to only use in the :class:`vtkm::cont::ArrayHandle`?
In this case, it is wasteful to have to copy the data, but you also do not want to be responsible for keeping the ``std::vector`` in scope.
To handle this, there is a special :func:`vtkm::cont::make_ArrayHandleMove` that will move the memory out of the ``std::vector`` and into the :class:`vtkm::cont::ArrayHandle`.
:func:`vtkm::cont::make_ArrayHandleMove` takes an "rvalue" version of a ``std::vector``.
To create an "rvalue", use the ``std::move`` function provided by C++.
Once :func:`vtkm::cont::make_ArrayHandleMove` is called, the provided ``std::vector`` becomes invalid and any further access to it is undefined.
This solution is shown in :exlineref:ex:ArrayOutOfScope:MoveVector`.
.. doxygenfunction:: vtkm::cont::make_ArrayHandleMove(std::vector<T,Allocator>&&)
.. doxygenfunction:: vtkm::cont::make_ArrayHandle(std::vector<T,Allocator>&&, vtkm::CopyFlag)
.. todo:: Document moving basic C arrays somewhere.
------------------------------
Deep Array Copies
------------------------------
.. index::
double: array handle; deep copy
As stated previously, an :class:`vtkm::cont::ArrayHandle` object behaves as a smart pointer that copies references to the data without copying the data itself.
This is clearly faster and more memory efficient than making copies of the data itself and usually the behavior desired.
However, it is sometimes the case that you need to make a separate copy of the data.
The easiest way to copy an :class:`vtkm::cont::ArrayHandle` is to use the :func:`vtkm::cont::ArrayHandle::DeepCopyFrom` method.
.. load-example:: ArrayHandleDeepCopy
:file: GuideExampleArrayHandle.cxx
:caption: Deep copy a :class:`vtkm::cont::ArrayHandle` of the same type.
However, the :func:`vtkm::cont::ArrayHandle::DeepCopyFrom` method only works if the two :class:`vtkm::cont::ArrayHandle` objects are the exact same type.
To simplify copying the data between :class:`vtkm::cont::ArrayHandle` objects of different types, |VTKm| comes with the :func:`vtkm::cont::ArrayCopy` convenience function defined in ``vtkm/cont/ArrayCopy.h``.
:func:`vtkm::cont::ArrayCopy` takes the array to copy from (the source) as its first argument and the array to copy to (the destination) as its second argument.
The destination array will be properly reallocated to the correct size.
.. load-example:: ArrayCopy
:file: GuideExampleRuntimeDeviceTracker.cxx
:caption: Using :func:`vtkm::cont::ArrayCopy`.
.. doxygenfunction:: vtkm::cont::ArrayCopy(const SourceArrayType&, DestArrayType&)
.. doxygenfunction:: vtkm::cont::ArrayCopy(const SourceArrayType&, vtkm::cont::UnknownArrayHandle&)
----------------------------------------
The Hidden Second Template Parameter
----------------------------------------
.. index::
double: array handle; storage
We have already seen that :class:`vtkm::cont::ArrayHandle` is a templated class with the template parameter indicating the type of values stored in the array.
However, :class:`vtkm::cont::ArrayHandle` has a second hidden parameter that indicates the \keyterm{storage} of the array.
We have so far been able to ignore this second template parameter because |VTKm| will assign a default storage for us that will store the data in a basic array.
Changing the storage of an :class:`vtkm::cont::ArrayHandle` lets us do many weird and wonderful things.
We will explore these options in later chapters, but for now we can ignore this second storage template parameter.
However, there are a couple of things to note concerning the storage.
First, if the compiler gives an error concerning your use of :class:`vtkm::cont::ArrayHandle`, the compiler will report the :class:`vtkm::cont::ArrayHandle` type with not one but two template parameters.
A second template parameter of :struct:`vtkm::cont::StorageTagBasic` can be ignored.
Second, if you write a function, method, or class that is templated based on an :class:`vtkm::cont::ArrayHandle` type, it is good practice to accept an :class:`vtkm::cont::ArrayHandle` with a non-default storage type.
There are two ways to do this.
The first way is to template both the value type and the storage type.
.. load-example:: ArrayHandleParameterTemplate
:file: GuideExampleArrayHandle.cxx
:caption: Templating a function on an :class:`vtkm::cont::ArrayHandle`'s parameters.
The second way is to template the whole array type rather than the sub types.
If you create a template where you expect one of the parameters to be an :class:`vtkm::cont::ArrayHandle`, you should use the :c:macro:`VTKM_IS_ARRAY_HANDLE` macro to verify that the type is indeed an :class:`vtkm::cont::ArrayHandle`.
.. doxygendefine:: VTKM_IS_ARRAY_HANDLE
.. load-example:: ArrayHandleFullTemplate
:file: GuideExampleArrayHandle.cxx
:caption: A template parameter that should be an :class:`vtkm::cont::ArrayHandle`.
------------------------------
Mutability
------------------------------
.. index:: array handle; const
One subtle feature of :class:`vtkm::cont::ArrayHandle` is that the class is, in principle, a pointer to an array pointer.
This means that the data in an :class:`vtkm::cont::ArrayHandle` is always mutable even if the class is declared ``const``.
You can change the contents of "constant" arrays via methods like :func:`vtkm::cont::ArrayHandle::WritePortal` and :func:`vtkm::cont::ArrayHandle::PrepareForOutput`.
It is even possible to change the underlying array allocation with methods like :func:`vtkm::cont::ArrayHandle::Allocate` and :func:`vtkm::cont::ArrayHandle::ReleaseResources`.
The upshot is that you can (sometimes) pass output arrays as constant :class:`vtkm::cont::ArrayHandle` references.
So if a constant :class:`vtkm::cont::ArrayHandle` can have its contents modified, what is the difference between a constant reference and a non-constant reference?
The difference is that the constant reference can change the array's content, but not the array itself.
Basically, this means that you cannot perform shallow copies into a ``const`` :class:`vtkm::cont::ArrayHandle`.
This can be a pretty big limitation, and many of |VTKm|'s internal device algorithms still require non-constant references for outputs.

@ -0,0 +1,118 @@
==============================
Basic Filter Implementation
==============================
.. index:: filter; implementation
:chapref:`simple-worklets:Simple Worklets` introduced the concept of a worklet and demonstrated how to create and run one to execute an algorithm on a device.
Although worklets provide a powerful mechanism for designing heavily threaded visualization algorithms, invoking them requires quite a bit of knowledge of the workings of |VTKm|.
Instead, most users execute algorithms in |VTKm| using filters.
Thus, to expose algorithms implemented with worklets to general users, we need to implement a filter to encapsulate the worklets.
In this chapter we will create a filter that encapsulates the worklet algorithm presented in :chapref:`simple-worklets:Simple Worklets`, which converted the units of a pressure field from pounds per square inch (psi) to Newtons per square meter (:math:`\mathrm{N}/\mathrm{m}^2`).
Filters in |VTKm| are implemented by deriving one of the filter base classes provided in ``vtkm::filter``.
There are multiple base filter classes that we can choose from.
These different classes are documented later in Chapter \ref{chap:FilterTypeReference}.
For this example we will use the :class:`vtkm::filter::FilterField` base class, which is used to derive one field from another field.
.. todo:: Fix above reference.
The following example shows the declaration of our pressure unit conversion filter.
By convention, this declaration would be placed in a header file with a ``.h`` extension.
|VTKm| filters are divided into libraries.
In this example, we are assuming this filter is being compiled in a library named ``vtkm::filter::unit_conversion``.
By convention, the source files would be placed in a directory named ``vtkm/filter/unit_conversion``.
.. load-example:: SimpleField
:file: GuideExampleSimpleAlgorithm.cxx
:caption: Header declaration for a simple filter.
It is typical for a filter to have a constructor to set up its initial state.
A filter will also override the :func:`vtkm::filter::Filter::DoExecute` method.
The :func:`vtkm::filter::Filter::DoExecute` method takes a :class:`vtkm::cont::DataSet` as input and likewise returns a :class:`vtkm::cont::DataSet` containing the results of the filter operation.
Note that the declaration of the ``PoundsPerSquareInchToNewtonsPerSquareMeterFilter`` contains the export macro ``VTKM_FILTER_UNIT_CONVERSION_EXPORT``.
This is a macro generated by CMake to handle the appropriate modifies for exporting a class from a library.
Remember that this code is to be placed in a library named ``vtkm::filter::unit_conversion``.
For this library, CMake creates a header file named ``vtkm/filter/unit_conversion.h`` that declares macros like ``VTKM_FILTER_UNIT_CONVERSION_EXPORT``.
.. didyouknow::
A filter can also override the :func:`vtkm::filter::Filter::DoExecutePartitions`, which operates on a :class:`vtkm::cont::PartitionedDataSet`.
If :func:`vtkm::filter::Filter::DoExecutePartitions` is not overridden, then the filter will call :func:`vtkm::filter::Filter::DoExecute` on each of the partitions and build a new :class:`vtkm::cont::PartitionedDataSet` with the outputs.
Once the filter class is declared in the ``.h`` file, the filter implementation is by convention given in a separate ``.cxx`` file.
Given the definition of our filter in :numref:`ex:SimpleField`, we will need to provide the implementation for the constructor and the :func:`vtkm::filter::Filter::DoExecute` method.
The constructor is quite simple.
It initializes the name of the output field name, which is managed by the superclass.
.. load-example:: SimpleFieldConstructor
:file: GuideExampleSimpleAlgorithm.cxx
:caption: Constructor for a simple filter.
In this case, we are setting the output field name to the empty string.
This is not to mean that the default name of the output field should be the empty string, which is not a good idea.
Rather, as we will see later, we will use the empty string to flag an output name that should be derived from the input name.
The meat of the filter implementation is located in the :func:`vtkm::filter::Filter::DoExecute` method.
.. load-example:: SimpleFieldDoExecute
:file: GuideExampleSimpleAlgorithm.cxx
:caption: Implementation of ``DoExecute`` for a simple filter.
The single argument to :func:`vtkm::filter::Filter::DoExecute` is a :class:`vtkm::cont::DataSet` containing the data to operate on, and :func:`vtkm::filter::Filter::DoExecute` returns a derived :class:`vtkm::cont::DataSet`.
The filter must pull the appropriate information out of the input :class:`vtkm::cont::DataSet` to operate on.
This simple algorithm just operates on a single field array of the data.
The :class:`vtkm::filter::FilterField` base class provides several methods to allow filter users to select the active field to operate on.
The filter implementation can get the appropriate field to operate on using the :func:`vtkm::filter::FilterField::GetFieldFromDataSet` method provided by :class:`vtkm::filter::FilterField` as shown in :exlineref:`ex:SimpleFieldDoExecute:InputField`.
One of the challenges with writing filters is determining the actual types the algorithm is operating on.
The :class:`vtkm::cont::Field` object pulled from the input :class:`vtkm::cont::DataSet` contains a :class:`vtkm::cont::ArrayHandle` (see :chapref:`basic-array-handles:Basic Array Handles`), but you do not know what the template parameters of the :class:`vtkm::cont::ArrayHandle` are.
There are numerous ways to extract an array of an unknown type out of a :class:`vtkm::cont::ArrayHandle` (many of which will be explored later in Chapter \ref{chap:UnknownArrayHandle}), but the :class:`vtkm::filter::FilterField` contains some convenience functions to simplify this.
.. todo:: Fix above reference.
In particular, this filter operates specifically on scalar fields.
For this purpose, :class:`vtkm::filter::FilterField` provides the :func:`vtkm::filter::FilterField::CastAndCallScalarField` helper method.
The first argument to :func:`vtkm::filter::FilterField::CastAndCallScalarField` is the field containing the data to operate on.
The second argument is a functor that will operate on the array once it is identified.
:func:`vtkm::filter::FilterField::CastAndCallScalarField` will pull a :class:`vtkm::cont::ArrayHandle` out of the field and call the provided functor with that object.
:func:`vtkm::filter::FilterField::CastAndCallScalarField` is called in :exlineref:`ex:SimpleFieldDoExecute:CastAndCall`.
.. didyouknow::
If your filter requires a field containing ``vtkm::Vec`s of a particular size (e.g. 3), you can use the convenience method :func:`vtkm::filter::FilterField::CastAndCallVecField`.
:func:`vtkm::filter::FilterField::CastAndCallVecField` works similarly to :func:`vtkm::filter::FilterField::CastAndCallScalarField` except that it takes a template parameter specifying the size of the ``vtkm::Vec`.
For example, ``vtkm::filter::FilterField::CastAndCallVecField<3>(inField, functor);``.
As previously stated, one of the arguments to :func:`vtkm::filter::FilterField::CastAndCallScalarField` is a functor that contains the routine to call with the found :class:`vtkm::cont::ArrayHandle`.
A functor can be created as its own ``class`` or ``struct``, but a more convenient method is to use a C++ lambda.
A lambda is an unnamed function defined inline with the code.
The lambda in :numref:`ex:SimpleFieldDoExecute` starts on :exlineref:`line {line}<ex:SimpleFieldDoExecute:Lambda>`.
Apart from being more convenient than creating a named class, lambda functions offer another important feature.
Lambda functions can "capture" variables in the current scope.
They can therefore access things like local variables and the ``this`` reference to the method's class (even accessing private members).
The callback to the lambda function in :numref:`ex:SimpleFieldDoExecute` first creates an output :class:`vtkm::cont::ArrayHandle` of a compatible type (:exlineref:`line {line}<ex:SimpleFieldDoExecute:CreateOutputArray>`), then invokes the worklet that computes the derived field (:exlineref:`line {line}<ex:SimpleFieldDoExecute:Invoke>`), and finally captures the resulting array.
Note that the :class:`vtkm::filter::Filter` base class provides a :func:`vtkm::filter::Filter::Invoke` member that can be used to invoke the worklet.
(See :secref:`simple-worklets:Invoking a Worklet` for information on invoking a worklet.)
Recall that the worklet created in :chapref:`simple-worklets:Simple Worklets` takes two parameters: an input array and an output array, which are shown in this invocation.
With the output data created, the filter has to build the output structure to return.
All implementations of :func:`vtkm::filter::Filter::DoExecute` must return a :class:`vtkm::cont::DataSet`, and for a simple field filter like this we want to return the same :class:`vtkm::cont::DataSet` as the input with the output field added.
The output field needs a name, and we get the appropriate name from the superclass (:exlineref:`ex:SimpleFieldDoExecute:OutputName`).
However, we would like a special case where if the user does not specify an output field name we construct one based on the input field name.
Recall from :numref:`ex:SimpleFieldConstructor` that by default we set the output field name to the empty string.
Thus, our filter checks for this empty string, and if it is encountered, it builds a field name by appending "_N/M^2" to it.
Finally, our filter constructs the output :class:`vtkm::cont::DataSet` using one of the :func:`vtkm::filter::Filter::CreateResult` member functions (:exlineref:`ex:SimpleFieldDoExecute:CreateResult`).
In this particular case, the filter uses :func:`vtkm::filter::FilterField::CreateResultField`, which constructs a :class:`vtkm::cont::DataSet` with the same structure as the input and adds the computed filter.
.. commonerrors::
The :func:`vtkm::filter::Filter::CreateResult` methods do more than just construct a new :class:`vtkm::cont::DataSet`.
They also set up the structure of the data and pass fields as specified by the state of the filter object.
Thus, implementations of :func:`vtkm::filter::Filter::DoExecute` should always return a :class:`vtkm::cont::DataSet` that is created with :func:`vtkm::filter::Filter::CreateResult` or a similarly named method in the base filter classes.
This chapter has just provided a brief introduction to creating filters.
There are several more filter superclasses to help express algorithms of different types.
After some more worklet concepts to implement more complex algorithms are introduced in :partref:`part-advanced:Advanced Development`, we will see a more complete documentation of the types of filters in Chapter \ref{chap:FilterTypeReference}.
.. todo:: Fix above reference.

@ -76,7 +76,7 @@ today_fmt = '%B %d, %Y'
rst_prolog = '''
.. |VTKm| replace:: VTKm
.. |report-year| replace:: 2023
.. |report-number| replace:: ORNL/TM-2023/2863
.. |report-number| replace:: ORNL/TM-2023/3182
'''
breathe_projects = { 'vtkm': '@doxygen_xml_output_dir@' }

@ -27,9 +27,7 @@ Building Data Sets
Before we go into detail on the cell sets, fields, and coordinate systems that make up a data set in |VTKm|, let us first discuss how to build a data set.
One simple way to build a data set is to load data from a file using the `vtkm::io` module.
Reading files is discussed in detail in Chapter~\ref{chap:FileIO}.
.. todo:: Add previous reference.
Reading files is discussed in detail in :chapref:`io:File I/O`.
This section describes building data sets of different types using a set of
classes named `DataSetBuilder*`, which provide a convenience layer
@ -42,9 +40,7 @@ on top of :class:`vtkm::cont::DataSet` to make it easier to create data sets.
Although it is sufficient for small data or data that come from a "slow" source, such as a file, it might be a bottleneck for large data generated by another library.
It is possible to adapt |VTKm|'s :class:`vtkm::cont::DataSet` to externally defined data.
This is done by wrapping existing data into what is called `ArrayHandle`, but this is a more advanced topic that will not be addressed in this chapter.
`ArrayHandle` objects are introduced in Chapter \ref{chap:BasicArrayHandles} and more adaptive techniques are described in later chapters.
.. todo:: Add previous reference.
`ArrayHandle` objects are introduced in :chapref:`basic-array-handles:Basic Array Handles` and more adaptive techniques are described in later chapters.
Creating Uniform Grids
==============================

@ -9,8 +9,10 @@
##============================================================================
set(examples
GuideExampleArrayHandle.cxx
GuideExampleColorTables.cxx
GuideExampleCoreDataTypes.cxx
GuideExampleEnvironmentModifierMacros.cxx
GuideExampleInitialization.cxx
GuideExampleIO.cxx
GuideExampleProvidedFilters.cxx
@ -19,8 +21,10 @@ set(examples
GuideExampleTimer.cxx
)
set(examples_device
GuideExampleDataSetCreation.cxx # Uses CellSetPartitioned
GuideExampleErrorHandling.cxx # Calls worklet
GuideExampleCellEdgesFaces.cxx
GuideExampleDataSetCreation.cxx
GuideExampleErrorHandling.cxx
GuideExampleSimpleAlgorithm.cxx
)
set(extra_libs)

@ -0,0 +1,538 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#define VTKM_NO_ERROR_ON_MIXED_CUDA_CXX_TAG
#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayPortalToIterators.h>
#include <vtkm/cont/ArrayRangeCompute.h>
#include <vtkm/cont/DeviceAdapter.h>
#include <vtkm/exec/FunctorBase.h>
#include <vtkm/cont/testing/Testing.h>
#include <algorithm>
#include <vector>
namespace
{
template<typename T>
vtkm::Float32 TestValue(T index)
{
return static_cast<vtkm::Float32>(1 + 0.001 * index);
}
void CheckArrayValues(const vtkm::cont::ArrayHandle<vtkm::Float32>& array,
vtkm::Float32 factor = 1)
{
// So far all the examples are using 50 entries. Could change.
VTKM_TEST_ASSERT(array.GetNumberOfValues() == 50, "Wrong number of values");
for (vtkm::Id index = 0; index < array.GetNumberOfValues(); index++)
{
VTKM_TEST_ASSERT(
test_equal(array.ReadPortal().Get(index), TestValue(index) * factor),
"Bad data value.");
}
}
////
//// BEGIN-EXAMPLE ArrayHandleParameterTemplate
////
template<typename T, typename Storage>
void Foo(const vtkm::cont::ArrayHandle<T, Storage>& array)
{
////
//// END-EXAMPLE ArrayHandleParameterTemplate
////
(void)array;
}
////
//// BEGIN-EXAMPLE ArrayHandleFullTemplate
////
template<typename ArrayType>
void Bar(const ArrayType& array)
{
VTKM_IS_ARRAY_HANDLE(ArrayType);
////
//// END-EXAMPLE ArrayHandleFullTemplate
////
(void)array;
}
void BasicConstruction()
{
////
//// BEGIN-EXAMPLE CreateArrayHandle
////
vtkm::cont::ArrayHandle<vtkm::Float32> outputArray;
////
//// END-EXAMPLE CreateArrayHandle
////
////
//// BEGIN-EXAMPLE ArrayHandleStorageParameter
////
vtkm::cont::ArrayHandle<vtkm::Float32, vtkm::cont::StorageTagBasic> arrayHandle;
////
//// END-EXAMPLE ArrayHandleStorageParameter
////
Foo(outputArray);
Bar(arrayHandle);
}
void ArrayHandleFromInitializerList()
{
////
//// BEGIN-EXAMPLE ArrayHandleFromInitializerList
////
auto fibonacciArray = vtkm::cont::make_ArrayHandle({ 0, 1, 1, 2, 3, 5, 8, 13 });
////
//// END-EXAMPLE ArrayHandleFromInitializerList
////
VTKM_TEST_ASSERT(fibonacciArray.GetNumberOfValues() == 8);
auto portal = fibonacciArray.ReadPortal();
VTKM_TEST_ASSERT(test_equal(portal.Get(0), 0));
VTKM_TEST_ASSERT(test_equal(portal.Get(1), 1));
VTKM_TEST_ASSERT(test_equal(portal.Get(2), 1));
VTKM_TEST_ASSERT(test_equal(portal.Get(3), 2));
VTKM_TEST_ASSERT(test_equal(portal.Get(4), 3));
VTKM_TEST_ASSERT(test_equal(portal.Get(5), 5));
VTKM_TEST_ASSERT(test_equal(portal.Get(6), 8));
VTKM_TEST_ASSERT(test_equal(portal.Get(7), 13));
////
//// BEGIN-EXAMPLE ArrayHandleFromInitializerListTyped
////
vtkm::cont::ArrayHandle<vtkm::FloatDefault> inputArray =
vtkm::cont::make_ArrayHandle<vtkm::FloatDefault>({ 1.4142f, 2.7183f, 3.1416f });
////
//// END-EXAMPLE ArrayHandleFromInitializerListTyped
////
VTKM_TEST_ASSERT(inputArray.GetNumberOfValues() == 3);
auto portal2 = inputArray.ReadPortal();
VTKM_TEST_ASSERT(test_equal(portal2.Get(0), 1.4142));
VTKM_TEST_ASSERT(test_equal(portal2.Get(1), 2.7183));
VTKM_TEST_ASSERT(test_equal(portal2.Get(2), 3.1416));
}
void ArrayHandleFromCArray()
{
////
//// BEGIN-EXAMPLE ArrayHandleFromCArray
////
vtkm::Float32 dataBuffer[50];
// Populate dataBuffer with meaningful data. Perhaps read data from a file.
//// PAUSE-EXAMPLE
for (vtkm::Id index = 0; index < 50; index++)
{
dataBuffer[index] = TestValue(index);
}
//// RESUME-EXAMPLE
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
vtkm::cont::make_ArrayHandle(dataBuffer, 50, vtkm::CopyFlag::On);
////
//// END-EXAMPLE ArrayHandleFromCArray
////
CheckArrayValues(inputArray);
}
vtkm::Float32 GetValueForArray(vtkm::Id index)
{
return TestValue(index);
}
void AllocateAndFillArrayHandle()
{
////
//// BEGIN-EXAMPLE ArrayHandlePopulate
////
////
//// BEGIN-EXAMPLE ArrayHandleAllocate
////
vtkm::cont::ArrayHandle<vtkm::Float32> arrayHandle;
const vtkm::Id ARRAY_SIZE = 50;
arrayHandle.Allocate(ARRAY_SIZE);
////
//// END-EXAMPLE ArrayHandleAllocate
////
// Usually it is easier to just use the auto keyword.
using PortalType = vtkm::cont::ArrayHandle<vtkm::Float32>::WritePortalType;
PortalType portal = arrayHandle.WritePortal();
for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); index++)
{
portal.Set(index, GetValueForArray(index));
}
////
//// END-EXAMPLE ArrayHandlePopulate
////
CheckArrayValues(arrayHandle);
{
vtkm::cont::ArrayHandle<vtkm::Float32> srcArray = arrayHandle;
vtkm::cont::ArrayHandle<vtkm::Float32> destArray;
////
//// BEGIN-EXAMPLE ArrayHandleDeepCopy
////
destArray.DeepCopyFrom(srcArray);
////
//// END-EXAMPLE ArrayHandleDeepCopy
////
VTKM_TEST_ASSERT(srcArray != destArray);
VTKM_TEST_ASSERT(test_equal_ArrayHandles(srcArray, destArray));
}
////
//// BEGIN-EXAMPLE ArrayRangeCompute
////
vtkm::cont::ArrayHandle<vtkm::Range> rangeArray =
vtkm::cont::ArrayRangeCompute(arrayHandle);
auto rangePortal = rangeArray.ReadPortal();
for (vtkm::Id index = 0; index < rangePortal.GetNumberOfValues(); ++index)
{
vtkm::Range componentRange = rangePortal.Get(index);
std::cout << "Values for component " << index << " go from " << componentRange.Min
<< " to " << componentRange.Max << std::endl;
}
////
//// END-EXAMPLE ArrayRangeCompute
////
vtkm::Range range = rangePortal.Get(0);
VTKM_TEST_ASSERT(test_equal(range.Min, TestValue(0)), "Bad min value.");
VTKM_TEST_ASSERT(test_equal(range.Max, TestValue(ARRAY_SIZE - 1)), "Bad max value.");
////
//// BEGIN-EXAMPLE ArrayHandleReallocate
////
// Add space for 10 more values at the end of the array.
arrayHandle.Allocate(arrayHandle.GetNumberOfValues() + 10, vtkm::CopyFlag::On);
////
//// END-EXAMPLE ArrayHandleReallocate
////
}
////
//// BEGIN-EXAMPLE ArrayOutOfScope
////
VTKM_CONT vtkm::cont::ArrayHandle<vtkm::Float32> BadDataLoad()
{
std::vector<vtkm::Float32> dataBuffer;
// Populate dataBuffer with meaningful data. Perhaps read data from a file.
//// PAUSE-EXAMPLE
dataBuffer.resize(50);
for (std::size_t index = 0; index < 50; index++)
{
dataBuffer[index] = TestValue(index);
}
//// RESUME-EXAMPLE
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
vtkm::cont::make_ArrayHandle(dataBuffer, vtkm::CopyFlag::Off);
//// PAUSE-EXAMPLE
CheckArrayValues(inputArray);
//// RESUME-EXAMPLE
return inputArray;
// THIS IS WRONG! At this point dataBuffer goes out of scope and deletes its
// memory. However, inputArray has a pointer to that memory, which becomes an
// invalid pointer in the returned object. Bad things will happen when the
// ArrayHandle is used.
}
VTKM_CONT vtkm::cont::ArrayHandle<vtkm::Float32> SafeDataLoad1()
{
////
//// BEGIN-EXAMPLE ArrayHandleFromVector
////
std::vector<vtkm::Float32> dataBuffer;
// Populate dataBuffer with meaningful data. Perhaps read data from a file.
//// PAUSE-EXAMPLE
dataBuffer.resize(50);
for (std::size_t index = 0; index < 50; index++)
{
dataBuffer[index] = TestValue(index);
}
//// RESUME-EXAMPLE
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
//// LABEL CopyFlagOn
vtkm::cont::make_ArrayHandle(dataBuffer, vtkm::CopyFlag::On);
////
//// END-EXAMPLE ArrayHandleFromVector
////
return inputArray;
// This is safe.
}
VTKM_CONT vtkm::cont::ArrayHandle<vtkm::Float32> SafeDataLoad2()
{
std::vector<vtkm::Float32> dataBuffer;
// Populate dataBuffer with meaningful data. Perhaps read data from a file.
//// PAUSE-EXAMPLE
dataBuffer.resize(50);
for (std::size_t index = 0; index < 50; index++)
{
dataBuffer[index] = TestValue(index);
}
//// RESUME-EXAMPLE
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray =
//// LABEL MoveVector
vtkm::cont::make_ArrayHandleMove(std::move(dataBuffer));
return inputArray;
// This is safe.
}
////
//// END-EXAMPLE ArrayOutOfScope
////
void ArrayHandleFromVector()
{
BadDataLoad();
}
void CheckSafeDataLoad()
{
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray1 = SafeDataLoad1();
CheckArrayValues(inputArray1);
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray2 = SafeDataLoad2();
CheckArrayValues(inputArray2);
}
////
//// BEGIN-EXAMPLE SimpleArrayPortal
////
template<typename T>
class SimpleScalarArrayPortal
{
public:
using ValueType = T;
// There is no specification for creating array portals, but they generally
// need a constructor like this to be practical.
VTKM_EXEC_CONT
SimpleScalarArrayPortal(ValueType* array, vtkm::Id numberOfValues)
: Array(array)
, NumberOfValues(numberOfValues)
{
}
VTKM_EXEC_CONT
SimpleScalarArrayPortal()
: Array(NULL)
, NumberOfValues(0)
{
}
VTKM_EXEC_CONT
vtkm::Id GetNumberOfValues() const { return this->NumberOfValues; }
VTKM_EXEC_CONT
ValueType Get(vtkm::Id index) const { return this->Array[index]; }
VTKM_EXEC_CONT
void Set(vtkm::Id index, ValueType value) const { this->Array[index] = value; }
private:
ValueType* Array;
vtkm::Id NumberOfValues;
};
////
//// END-EXAMPLE SimpleArrayPortal
////
////
//// BEGIN-EXAMPLE ArrayPortalToIterators
////
template<typename PortalType>
VTKM_CONT std::vector<typename PortalType::ValueType> CopyArrayPortalToVector(
const PortalType& portal)
{
using ValueType = typename PortalType::ValueType;
std::vector<ValueType> result(static_cast<std::size_t>(portal.GetNumberOfValues()));
vtkm::cont::ArrayPortalToIterators<PortalType> iterators(portal);
std::copy(iterators.GetBegin(), iterators.GetEnd(), result.begin());
return result;
}
////
//// END-EXAMPLE ArrayPortalToIterators
////
void TestArrayPortalVectors()
{
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray = SafeDataLoad1();
std::vector<vtkm::Float32> buffer = CopyArrayPortalToVector(inputArray.ReadPortal());
VTKM_TEST_ASSERT(static_cast<vtkm::Id>(buffer.size()) ==
inputArray.GetNumberOfValues(),
"Vector was sized wrong.");
for (vtkm::Id index = 0; index < inputArray.GetNumberOfValues(); index++)
{
VTKM_TEST_ASSERT(
test_equal(buffer[static_cast<std::size_t>(index)], TestValue(index)),
"Bad data value.");
}
SimpleScalarArrayPortal<vtkm::Float32> portal(&buffer.at(0),
static_cast<vtkm::Id>(buffer.size()));
////
//// BEGIN-EXAMPLE ArrayPortalToIteratorBeginEnd
////
std::vector<vtkm::Float32> myContainer(
static_cast<std::size_t>(portal.GetNumberOfValues()));
std::copy(vtkm::cont::ArrayPortalToIteratorBegin(portal),
vtkm::cont::ArrayPortalToIteratorEnd(portal),
myContainer.begin());
////
//// END-EXAMPLE ArrayPortalToIteratorBeginEnd
////
for (vtkm::Id index = 0; index < inputArray.GetNumberOfValues(); index++)
{
VTKM_TEST_ASSERT(
test_equal(myContainer[static_cast<std::size_t>(index)], TestValue(index)),
"Bad data value.");
}
}
////
//// BEGIN-EXAMPLE ControlPortals
////
template<typename T, typename Storage>
void SortCheckArrayHandle(vtkm::cont::ArrayHandle<T, Storage> arrayHandle)
{
using WritePortalType = typename vtkm::cont::ArrayHandle<T, Storage>::WritePortalType;
using ReadPortalType = typename vtkm::cont::ArrayHandle<T, Storage>::ReadPortalType;
WritePortalType readwritePortal = arrayHandle.WritePortal();
// This is actually pretty dumb. Sorting would be generally faster in
// parallel in the execution environment using the device adapter algorithms.
std::sort(vtkm::cont::ArrayPortalToIteratorBegin(readwritePortal),
vtkm::cont::ArrayPortalToIteratorEnd(readwritePortal));
ReadPortalType readPortal = arrayHandle.ReadPortal();
for (vtkm::Id index = 1; index < readPortal.GetNumberOfValues(); index++)
{
if (readPortal.Get(index - 1) > readPortal.Get(index))
{
//// PAUSE-EXAMPLE
VTKM_TEST_FAIL("Sorting is wrong!");
//// RESUME-EXAMPLE
std::cout << "Sorting is wrong!" << std::endl;
break;
}
}
}
////
//// END-EXAMPLE ControlPortals
////
void TestControlPortalsExample()
{
SortCheckArrayHandle(SafeDataLoad2());
}
////
//// BEGIN-EXAMPLE ExecutionPortals
////
template<typename InputPortalType, typename OutputPortalType>
struct DoubleFunctor : public vtkm::exec::FunctorBase
{
InputPortalType InputPortal;
OutputPortalType OutputPortal;
VTKM_CONT
DoubleFunctor(InputPortalType inputPortal, OutputPortalType outputPortal)
: InputPortal(inputPortal)
, OutputPortal(outputPortal)
{
}
VTKM_EXEC
void operator()(vtkm::Id index) const
{
this->OutputPortal.Set(index, 2 * this->InputPortal.Get(index));
}
};
template<typename T, typename Device>
void DoubleArray(vtkm::cont::ArrayHandle<T> inputArray,
vtkm::cont::ArrayHandle<T> outputArray,
Device)
{
vtkm::Id numValues = inputArray.GetNumberOfValues();
vtkm::cont::Token token;
auto inputPortal = inputArray.PrepareForInput(Device{}, token);
auto outputPortal = outputArray.PrepareForOutput(numValues, Device{}, token);
// Token is now attached to inputPortal and outputPortal. Those two portals
// are guaranteed to be valid until token goes out of scope at the end of
// this function.
DoubleFunctor<decltype(inputPortal), decltype(outputPortal)> functor(inputPortal,
outputPortal);
vtkm::cont::DeviceAdapterAlgorithm<Device>::Schedule(functor, numValues);
}
////
//// END-EXAMPLE ExecutionPortals
////
void TestExecutionPortalsExample()
{
vtkm::cont::ArrayHandle<vtkm::Float32> inputArray = SafeDataLoad1();
CheckArrayValues(inputArray);
vtkm::cont::ArrayHandle<vtkm::Float32> outputArray;
DoubleArray(inputArray, outputArray, vtkm::cont::DeviceAdapterTagSerial());
CheckArrayValues(outputArray, 2);
}
void Test()
{
BasicConstruction();
ArrayHandleFromInitializerList();
ArrayHandleFromCArray();
ArrayHandleFromVector();
AllocateAndFillArrayHandle();
CheckSafeDataLoad();
TestArrayPortalVectors();
TestControlPortalsExample();
TestExecutionPortalsExample();
}
} // anonymous namespace
int GuideExampleArrayHandle(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(Test, argc, argv);
}

@ -0,0 +1,321 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/exec/CellEdge.h>
#include <vtkm/exec/CellFace.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
#include <vtkm/worklet/ScatterCounting.h>
#include <vtkm/worklet/WorkletMapTopology.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayHandleGroupVec.h>
#include <vtkm/cont/ArrayHandleGroupVecVariable.h>
#include <vtkm/cont/CellSetSingleType.h>
#include <vtkm/cont/ConvertNumComponentsToOffsets.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
namespace
{
struct ExtractEdges
{
////
//// BEGIN-EXAMPLE CellEdge
////
struct EdgesCount : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn, FieldOutCell numEdgesInCell);
using ExecutionSignature = void(CellShape, PointCount, _2);
using InputDomain = _1;
template<typename CellShapeTag>
VTKM_EXEC void operator()(CellShapeTag cellShape,
vtkm::IdComponent numPointsInCell,
vtkm::IdComponent& numEdges) const
{
vtkm::ErrorCode status =
vtkm::exec::CellEdgeNumberOfEdges(numPointsInCell, cellShape, numEdges);
if (status != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(status));
}
}
};
////
//// BEGIN-EXAMPLE ComplexWorklet
////
struct EdgesExtract : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn, FieldOutCell edgeIndices);
using ExecutionSignature = void(CellShape, PointIndices, VisitIndex, _2);
using InputDomain = _1;
using ScatterType = vtkm::worklet::ScatterCounting;
template<typename CellShapeTag,
typename PointIndexVecType,
typename EdgeIndexVecType>
VTKM_EXEC void operator()(CellShapeTag cellShape,
const PointIndexVecType& globalPointIndicesForCell,
vtkm::IdComponent edgeIndex,
EdgeIndexVecType& edgeIndices) const
{
////
//// END-EXAMPLE ComplexWorklet
////
vtkm::IdComponent numPointsInCell =
globalPointIndicesForCell.GetNumberOfComponents();
vtkm::ErrorCode error;
vtkm::IdComponent pointInCellIndex0;
error = vtkm::exec::CellEdgeLocalIndex(
numPointsInCell, 0, edgeIndex, cellShape, pointInCellIndex0);
if (error != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(error));
return;
}
vtkm::IdComponent pointInCellIndex1;
error = vtkm::exec::CellEdgeLocalIndex(
numPointsInCell, 1, edgeIndex, cellShape, pointInCellIndex1);
if (error != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(error));
return;
}
edgeIndices[0] = globalPointIndicesForCell[pointInCellIndex0];
edgeIndices[1] = globalPointIndicesForCell[pointInCellIndex1];
}
};
////
//// END-EXAMPLE CellEdge
////
template<typename CellSetInType>
VTKM_CONT vtkm::cont::CellSetSingleType<> Run(const CellSetInType& cellSetIn)
{
// Count how many edges each cell has
vtkm::cont::ArrayHandle<vtkm::IdComponent> edgeCounts;
vtkm::worklet::DispatcherMapTopology<EdgesCount> countDispatcher;
countDispatcher.Invoke(cellSetIn, edgeCounts);
// Set up a "scatter" to create an output entry for each edge in the input
vtkm::worklet::ScatterCounting scatter(edgeCounts);
// Get the cell index array for all the edges
vtkm::cont::ArrayHandle<vtkm::Id> edgeIndices;
vtkm::worklet::DispatcherMapTopology<EdgesExtract> extractDispatcher(scatter);
extractDispatcher.Invoke(cellSetIn,
vtkm::cont::make_ArrayHandleGroupVec<2>(edgeIndices));
// Construct the resulting cell set and return
vtkm::cont::CellSetSingleType<> cellSetOut;
cellSetOut.Fill(
cellSetIn.GetNumberOfPoints(), vtkm::CELL_SHAPE_LINE, 2, edgeIndices);
return cellSetOut;
}
};
void TryExtractEdges()
{
std::cout << "Trying extract edges worklets." << std::endl;
vtkm::cont::DataSet dataSet =
vtkm::cont::testing::MakeTestDataSet().Make3DExplicitDataSet5();
ExtractEdges extractEdges;
vtkm::cont::CellSetSingleType<> edgeCells = extractEdges.Run(dataSet.GetCellSet());
VTKM_TEST_ASSERT(edgeCells.GetNumberOfPoints() == 11,
"Output has wrong number of points");
VTKM_TEST_ASSERT(edgeCells.GetNumberOfCells() == 35,
"Output has wrong number of cells");
}
struct ExtractFaces
{
////
//// BEGIN-EXAMPLE CellFace
////
struct FacesCount : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn, FieldOutCell numFacesInCell);
using ExecutionSignature = void(CellShape, _2);
using InputDomain = _1;
template<typename CellShapeTag>
VTKM_EXEC void operator()(CellShapeTag cellShape, vtkm::IdComponent& numFaces) const
{
vtkm::ErrorCode status = vtkm::exec::CellFaceNumberOfFaces(cellShape, numFaces);
if (status != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(status));
}
}
};
struct FacesCountPoints : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn,
FieldOutCell numPointsInFace,
FieldOutCell faceShape);
using ExecutionSignature = void(CellShape, VisitIndex, _2, _3);
using InputDomain = _1;
using ScatterType = vtkm::worklet::ScatterCounting;
template<typename CellShapeTag>
VTKM_EXEC void operator()(CellShapeTag cellShape,
vtkm::IdComponent faceIndex,
vtkm::IdComponent& numPointsInFace,
vtkm::UInt8& faceShape) const
{
vtkm::exec::CellFaceNumberOfPoints(faceIndex, cellShape, numPointsInFace);
switch (numPointsInFace)
{
case 3:
faceShape = vtkm::CELL_SHAPE_TRIANGLE;
break;
case 4:
faceShape = vtkm::CELL_SHAPE_QUAD;
break;
default:
faceShape = vtkm::CELL_SHAPE_POLYGON;
break;
}
}
};
struct FacesExtract : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn, FieldOutCell faceIndices);
using ExecutionSignature = void(CellShape, PointIndices, VisitIndex, _2);
using InputDomain = _1;
using ScatterType = vtkm::worklet::ScatterCounting;
template<typename CellShapeTag,
typename PointIndexVecType,
typename FaceIndexVecType>
VTKM_EXEC void operator()(CellShapeTag cellShape,
const PointIndexVecType& globalPointIndicesForCell,
vtkm::IdComponent faceIndex,
FaceIndexVecType& faceIndices) const
{
vtkm::IdComponent numPointsInFace = faceIndices.GetNumberOfComponents();
for (vtkm::IdComponent pointInFaceIndex = 0; pointInFaceIndex < numPointsInFace;
pointInFaceIndex++)
{
vtkm::IdComponent pointInCellIndex;
vtkm::ErrorCode error = vtkm::exec::CellFaceLocalIndex(
pointInFaceIndex, faceIndex, cellShape, pointInCellIndex);
if (error != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(error));
return;
}
faceIndices[pointInFaceIndex] = globalPointIndicesForCell[pointInCellIndex];
}
}
};
////
//// END-EXAMPLE CellFace
////
template<typename CellSetInType>
VTKM_CONT vtkm::cont::CellSetExplicit<> Run(const CellSetInType& cellSetIn)
{
// Count how many faces each cell has
vtkm::cont::ArrayHandle<vtkm::IdComponent> faceCounts;
vtkm::worklet::DispatcherMapTopology<FacesCount> countDispatcher;
countDispatcher.Invoke(cellSetIn, faceCounts);
// Set up a "scatter" to create an output entry for each face in the input
vtkm::worklet::ScatterCounting scatter(faceCounts);
// Count how many points each face has. Also get the shape of each face.
vtkm::cont::ArrayHandle<vtkm::IdComponent> pointsPerFace;
vtkm::cont::ArrayHandle<vtkm::UInt8> faceShapes;
vtkm::worklet::DispatcherMapTopology<FacesCountPoints> countPointsDispatcher(
scatter);
countPointsDispatcher.Invoke(cellSetIn, pointsPerFace, faceShapes);
// To construct an ArrayHandleGroupVecVariable, we need to convert
// pointsPerFace to an array of offsets
vtkm::Id faceIndicesSize;
vtkm::cont::ArrayHandle<vtkm::Id> faceIndexOffsets =
vtkm::cont::ConvertNumComponentsToOffsets(pointsPerFace, faceIndicesSize);
// We need to preallocate the array for faceIndices (because that is the
// way ArrayHandleGroupVecVariable works). We use the value previously
// returned from ConvertNumComponentsToOffsets.
vtkm::cont::ArrayHandle<vtkm::Id> faceIndices;
faceIndices.Allocate(faceIndicesSize);
// Get the cell index array for all the faces
vtkm::worklet::DispatcherMapTopology<FacesExtract> extractDispatcher(scatter);
extractDispatcher.Invoke(
cellSetIn,
vtkm::cont::make_ArrayHandleGroupVecVariable(faceIndices, faceIndexOffsets));
// Construct the resulting cell set and return
vtkm::cont::CellSetExplicit<> cellSetOut;
cellSetOut.Fill(
cellSetIn.GetNumberOfPoints(), faceShapes, faceIndices, faceIndexOffsets);
return cellSetOut;
}
};
void TryExtractFaces()
{
std::cout << "Trying extract faces worklets." << std::endl;
vtkm::cont::DataSet dataSet =
vtkm::cont::testing::MakeTestDataSet().Make3DExplicitDataSet5();
ExtractFaces extractFaces;
vtkm::cont::CellSetExplicit<> faceCells = extractFaces.Run(dataSet.GetCellSet());
VTKM_TEST_ASSERT(faceCells.GetNumberOfPoints() == 11,
"Output has wrong number of points");
VTKM_TEST_ASSERT(faceCells.GetNumberOfCells() == 20,
"Output has wrong number of cells");
VTKM_TEST_ASSERT(faceCells.GetCellShape(0) == vtkm::CELL_SHAPE_QUAD, "Face wrong");
vtkm::Id4 quadIndices;
faceCells.GetIndices(0, quadIndices);
VTKM_TEST_ASSERT(test_equal(quadIndices, vtkm::Id4(0, 3, 7, 4)), "Face wrong");
VTKM_TEST_ASSERT(faceCells.GetCellShape(12) == vtkm::CELL_SHAPE_TRIANGLE,
"Face wrong");
vtkm::Id3 triIndices;
faceCells.GetIndices(12, triIndices);
VTKM_TEST_ASSERT(test_equal(triIndices, vtkm::Id3(8, 10, 6)), "Face wrong");
}
void Run()
{
TryExtractEdges();
TryExtractFaces();
}
} // anonymous namespace
int GuideExampleCellEdgesFaces(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(Run, argc, argv);
}

@ -0,0 +1,74 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/Types.h>
#include <vtkm/testing/Testing.h>
namespace
{
////
//// BEGIN-EXAMPLE EnvironmentModifierMacro
////
template<typename ValueType>
VTKM_EXEC_CONT ValueType Square(const ValueType& inValue)
{
return inValue * inValue;
}
////
//// END-EXAMPLE EnvironmentModifierMacro
////
////
//// BEGIN-EXAMPLE SuppressExecWarnings
////
VTKM_SUPPRESS_EXEC_WARNINGS
template<typename Functor>
VTKM_EXEC_CONT void OverlyComplicatedForLoop(Functor& functor, vtkm::Id numInterations)
{
for (vtkm::Id index = 0; index < numInterations; index++)
{
functor();
}
}
////
//// END-EXAMPLE SuppressExecWarnings
////
struct TestFunctor
{
vtkm::Id Count;
VTKM_CONT
TestFunctor()
: Count(0)
{
}
VTKM_CONT
void operator()() { this->Count++; }
};
void Test()
{
VTKM_TEST_ASSERT(Square(2) == 4, "Square function doesn't square.");
TestFunctor functor;
OverlyComplicatedForLoop(functor, 10);
VTKM_TEST_ASSERT(functor.Count == 10, "Bad iterations.");
}
} // anonymous namespace
int GuideExampleEnvironmentModifierMacros(int argc, char* argv[])
{
return vtkm::testing::Testing::Run(Test, argc, argv);
}

@ -0,0 +1,205 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/filter/FilterField.h>
#include <vtkm/cont/Invoker.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
namespace
{
constexpr vtkm::Id ARRAY_SIZE = 10;
////
//// BEGIN-EXAMPLE SimpleWorklet
////
//// LABEL Inherit
struct PoundsPerSquareInchToNewtonsPerSquareMeterWorklet : vtkm::worklet::WorkletMapField
{
//// LABEL ControlSignature
//// BEGIN-EXAMPLE ControlSignature
using ControlSignature = void(FieldIn psi, FieldOut nsm);
//// END-EXAMPLE ControlSignature
//// LABEL ExecutionSignature
//// BEGIN-EXAMPLE ExecutionSignature
using ExecutionSignature = void(_1, _2);
//// END-EXAMPLE ExecutionSignature
//// LABEL InputDomain
//// BEGIN-EXAMPLE InputDomain
using InputDomain = _1;
//// END-EXAMPLE InputDomain
//// LABEL OperatorStart
//// BEGIN-EXAMPLE WorkletOperator
template<typename T>
VTKM_EXEC void operator()(const T& psi, T& nsm) const
{
//// END-EXAMPLE WorkletOperator
// 1 psi = 6894.76 N/m^2
nsm = T(6894.76f) * psi;
//// LABEL OperatorEnd
}
};
////
//// END-EXAMPLE SimpleWorklet
////
void DemoWorklet()
{
////
//// BEGIN-EXAMPLE WorkletInvoke
////
vtkm::cont::ArrayHandle<vtkm::FloatDefault> psiArray;
// Fill psiArray with values...
//// PAUSE-EXAMPLE
psiArray.Allocate(ARRAY_SIZE);
SetPortal(psiArray.WritePortal());
//// RESUME-EXAMPLE
//// LABEL Construct
vtkm::cont::Invoker invoke;
vtkm::cont::ArrayHandle<vtkm::FloatDefault> nsmArray;
//// LABEL Call
invoke(PoundsPerSquareInchToNewtonsPerSquareMeterWorklet{}, psiArray, nsmArray);
////
//// END-EXAMPLE WorkletInvoke
////
}
} // anonymous namespace
#define VTKM_FILTER_UNIT_CONVERSION_EXPORT
////
//// BEGIN-EXAMPLE SimpleField
////
namespace vtkm
{
namespace filter
{
namespace unit_conversion
{
class VTKM_FILTER_UNIT_CONVERSION_EXPORT PoundsPerSquareInchToNewtonsPerSquareMeterFilter
: public vtkm::filter::FilterField
{
public:
VTKM_CONT PoundsPerSquareInchToNewtonsPerSquareMeterFilter();
VTKM_CONT vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& inDataSet) override;
};
}
}
} // namespace vtkm::filter::unit_conversion
////
//// END-EXAMPLE SimpleField
////
namespace vtkm
{
namespace filter
{
namespace unit_conversion
{
////
//// BEGIN-EXAMPLE SimpleFieldConstructor
////
VTKM_CONT PoundsPerSquareInchToNewtonsPerSquareMeterFilter::
PoundsPerSquareInchToNewtonsPerSquareMeterFilter()
{
this->SetOutputFieldName("");
}
////
//// END-EXAMPLE SimpleFieldConstructor
////
////
//// BEGIN-EXAMPLE SimpleFieldDoExecute
////
VTKM_CONT vtkm::cont::DataSet
PoundsPerSquareInchToNewtonsPerSquareMeterFilter::DoExecute(
const vtkm::cont::DataSet& inDataSet)
{
//// LABEL InputField
vtkm::cont::Field inField = this->GetFieldFromDataSet(inDataSet);
vtkm::cont::UnknownArrayHandle outArray;
//// LABEL Lambda
auto resolveType = [&](const auto& inputArray) {
// use std::decay to remove const ref from the decltype of concrete.
using T = typename std::decay_t<decltype(inputArray)>::ValueType;
//// LABEL CreateOutputArray
vtkm::cont::ArrayHandle<T> result;
//// LABEL Invoke
this->Invoke(
PoundsPerSquareInchToNewtonsPerSquareMeterWorklet{}, inputArray, result);
outArray = result;
};
//// LABEL CastAndCall
this->CastAndCallScalarField(inField, resolveType);
//// LABEL OutputName
std::string outFieldName = this->GetOutputFieldName();
if (outFieldName == "")
{
outFieldName = inField.GetName() + "_N/m^2";
}
//// LABEL CreateResult
return this->CreateResultField(
inDataSet, outFieldName, inField.GetAssociation(), outArray);
}
////
//// END-EXAMPLE SimpleFieldDoExecute
////
}
}
} // namespace vtkm::filter::unit_conversion
namespace
{
void DemoFilter()
{
vtkm::cont::testing::MakeTestDataSet makeData;
vtkm::cont::DataSet inData = makeData.Make3DExplicitDataSet0();
vtkm::filter::unit_conversion::PoundsPerSquareInchToNewtonsPerSquareMeterFilter
convertFilter;
convertFilter.SetActiveField("pointvar");
vtkm::cont::DataSet outData = convertFilter.Execute(inData);
outData.PrintSummary(std::cout);
VTKM_TEST_ASSERT(outData.HasPointField("pointvar_N/m^2"));
}
void Run()
{
DemoWorklet();
DemoFilter();
}
} // anonymous namespace
int GuideExampleSimpleAlgorithm(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(Run, argc, argv);
}

@ -0,0 +1,160 @@
==============================
General Approach
==============================
|VTKm| is designed to provide a pervasive parallelism throughout all its visualization algorithms, meaning that the algorithm is designed to operate with independent concurrency at the finest possible level throughout.
|VTKm| provides this pervasive parallelism by providing a programming construct called a :index:`worklet`, which operates on a very fine granularity of data.
The worklets are designed as serial components, and |VTKm| handles whatever layers of concurrency are necessary, thereby removing the onus from the visualization algorithm developer.
Worklet operation is then wrapped into :index:`filter`, which provide a simplified interface to end users.
A worklet is essentially a :index:`functor` or :index:`kernel` designed to operate on a small element of data.
(The name "worklet" means work on a small amount of data.)
The worklet is constrained to contain a serial and stateless function.
These constraints form three critical purposes.
First, the constraints on the worklets allow |VTKm| to schedule worklet invocations on a great many independent concurrent threads and thereby making the algorithm pervasively parallel.
Second, the constraints allow |VTKm| to provide thread safety.
By controlling the memory access the toolkit can insure that no worklet will have any memory collisions, false sharing, or other parallel programming pitfalls.
Third, the constraints encourage good programming practices.
The worklet model provides a natural approach to visualization algorithm design that also has good general performance characteristics.
|VTKm| allows developers to design algorithms that are run on massive amounts of threads.
However, |VTKm| also allows developers to interface to applications, define data, and invoke algorithms that they have written or are provided otherwise.
These two modes represent significantly different operations on the data.
The operating code of an algorithm in a worklet is constrained to access only a small portion of data that is provided by the framework.
Conversely, code that is building the data structures needs to manage the data in its entirety, but has little reason to perform computations on any particular element.
.. index:: environment
Consequently, |VTKm| is divided into two environments that handle each of these use cases.
Each environment has its own API, and direct interaction between the environments is disallowed.
The environments are as follows.
.. index::
double: environment; execution
* **Execution Environment**
This is the environment in which the computational portion of algorithms are executed.
The API for this environment provides work for one element with convenient access to information such as connectivity and neighborhood as needed by typical visualization algorithms.
Code for the execution environment is designed to always execute on a very large number of threads.
.. index::
double: environment; control
* **Control Environment**
This is the environment that is used to interface with applications, interface with I/O devices, and schedule parallel execution of the algorithms.
The associated API is designed for users that want to use |VTKm| to analyze their data using provided or supplied filters.
Code for the control environment is designed to run on a single thread (or one single thread per process in an MPI job).
These dual programming environments are partially a convenience to isolate the application from the execution of the worklets and are partially a necessity to support GPU languages with host and device environments.
The control and execution environments are logically equivalent to the host and device environments, respectively, in CUDA and other associated GPU languages.
.. figure:: images/VTKmEnvironments.png
:width: 100%
:name: fig:VTKmDiagram
Diagram of the |VTKm| framework.
:numref:`fig:VTKmDiagram` displays the relationship between the control and execution environment.
The typical workflow when using |VTKm| is that first the control thread establishes a data set in the control environment and then invokes a parallel operation on the data using a filter.
From there the data is logically divided into its constituent elements, which are sent to independent invocations of a worklet.
The worklet invocations, being independent, are run on as many concurrent threads as are supported by the device.
On completion the results of the worklet invocations are collected to a single data structure and a handle is returned back to the control environment.
.. didyouknow::
Are you only planning to use filters in |VTKm| that already exist?
If so, then everything you work with will be in the control environment.
The execution environment is only used when implementing algorithms for filters.
------------------------------
Package Structure
------------------------------
.. index::
single: packages
single: namespace
|VTKm| is organized in a hierarchy of nested packages.
|VTKm| places definitions in namespaces that correspond to the package (with the exception that one package may specialize a template defined in a different namespace).
The base package is named ``vtkm``.
All classes within |VTKm| are placed either directly in the ``vtkm`` package or in a package beneath it.
This helps prevent name collisions between |VTKm| and any other library.
.. index::
single: environment
double: control; environment
double: execution; environment
As described at the beginning of this chapter, the |VTKm| API is divided into two distinct environments: the control environment and the execution environment.
The API for these two environments are located in the ``vtkm::cont`` and ``vtkmexec`` packages, respectively.
Items located in the base ``vtkm`` namespace are available in both environments.
.. didyouknow::
Although it is conventional to spell out names in identifiers (as outlined in https://gitlab.kitware.com/vtk/vtk-m/blob/master/docs/CodingConventions.md) there is an exception to abbreviate control and execution to ``cont`` and ``exec``, respectively.
This is because it is also part of the coding convention to declare the entire namespace when using an identifier that is part of the corresponding package.
The shorter names make the identifiers easier to read, faster to type, and more feasible to pack lines in terminal displays.
These abbreviations are also used instead of more common abbreviations (e.g. ctrl for control) because, as part of actual English words, they are easier to type.
Further functionality in |VTKm| is built on top of the base ``vtkm``, ``vtkm::cont``, and ``vtkm::exec`` packages.
Support classes for building worklets, introduced in Chapter :chapref:`simple-worklets:Simple Worklets`, are contained in the ``vtkm::worklet`` package.
Other facilities in |VTKm| are provided in their own packages such as ``vtkm::io``, ``vtkm::filter``, and ``vtkm::rendering``.
These packages are described in :partref:`part-using:Using |VTKm|`.
|VTKm| contains code that uses specialized compiler features, such as those with CUDA, or libraries, such as Kokkos, that will not be available on all machines.
Code for these features are encapsulated in their own packages under the ``vtkm::cont`` namespace: ``vtkm::cont::cuda`` and ``vtkm::cont::kokkos``.
By convention all classes will be defined in a file with the same name as the class name (with a ``.h`` extension) located in a directory corresponding to the package name.
For example, the :class:`vtkm::cont::DataSet` class is found in the ``vtkm/cont/DataSet.h`` header.
There are, however, exceptions to this rule.
Some smaller classes and types are grouped together for convenience.
These exceptions will be noted as necessary.
Within each namespace there may also be ``internal`` and ``detail`` sub-namespaces.
The ``internal`` namespaces contain features that are used internally and may change without notice.
The ``detail`` namespaces contain features that are used by a particular class but must be declared outside of that class.
Users should generally ignore classes in these namespaces.
--------------------------------------------------
Function and Method Environment Modifiers
--------------------------------------------------
Any function or method defined by |VTKm| must come with a modifier that determines in which environments the function may be run.
These modifiers are C macros that |VTKm| uses to instruct the compiler for which architectures to compile each method.
Most user code outside of |VTKm| need not use these macros with the important exception of any classes passed to |VTKm|.
This occurs when defining new worklets, array storage, and device adapters.
.. index::
single: function modifier
single: method modifier
double: modifier; control
double: modifier; execution
|VTKm| provides three modifier macros, ``VTKM_CONT``, ``VTKM_EXEC``, and ``VTKM_EXEC_CONT``, which are used to declare functions and methods that can run in the control environment, execution environment, and both environments, respectively.
These macros get defined by including just about any |VTKm| header file, but including ``vtkm/Types.h`` will ensure they are defined.
The modifier macro is placed after the template declaration, if there is one, and before the return type for the function.
Here is a simple example of a function that will square a value.
Since most types you would use this function on have operators in both the control and execution environments, the function is declared for both places.
.. load-example:: EnvironmentModifierMacro
:file: GuideExampleEnvironmentModifierMacros.cxx
:caption: Usage of an environment modifier macro on a function.
.. index::
single: __host__
single: __device__
The primary function of the modifier macros is to inject compiler-specific keywords that specify what architecture to compile code for.
For example, when compiling with :index:`CUDA`, the control modifiers have ``__host__`` in them and execution modifiers have ``__device__`` in them.
It is sometimes the case that a function declared as ``VTKM_EXEC_CONT`` has to call a method declared as ``VTKM_EXEC`` or ``VTKM_CONT``.
Generally functions should not call other functions with incompatible control/execution modifiers, but sometimes a generic ``VTKM_EXEC_CONT`` function calls another function determined by the template parameters, and the valid environments of this subfunction may be inconsistent.
For cases like this, you can use the ``VTKM_SUPPRESS_EXEC_WARNINGS`` to tell the compiler to ignore the inconsistency when resolving the template.
When applied to a templated function or method, ``VTKM_SUPPRESS_EXEC_WARNINGS`` is placed before the ``template`` keyword.
When applied to a non-templated method in a templated class, ``VTKM_SUPPRESS_EXEC_WARNINGS`` is placed before the environment modifier macro.
.. load-example:: SuppressExecWarnings
:file: GuideExampleEnvironmentModifierMacros.cxx
:caption: Suppressing warnings about functions from mixed environments.

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0c42d741b4395b52d79a67253e6a988494a97efc4e4d07e4e8adcc15ecdbb2f2
size 990677

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e46de784370fb0beb9c6bc5fca80855761a0cced9bdab6a381f73b07f0b7f733
size 132676

@ -60,7 +60,7 @@ The :class:`vtkm::Sphere` is defined by a center location and a radius, which ar
An example :class:`vtkm::Sphere` is shown in :numref:`fig:ImplicitSphere`.
.. figure:: images/ImplicitSphere.png
:width: width=2.5in
:width: 2.5in
:name: fig:ImplicitSphere
Visual Representation of an Implicit Sphere.

@ -156,12 +156,10 @@ This is the default behavior of :class:`vtkm::cont::ScopedRuntimeDeviceTracker`,
.. doxygenenum:: RuntimeDeviceTrackerMode
As a motivating example, let us say that we want to perform a deep copy of an array (described in Section~\ref{sec:DeepArrayCopies}).
As a motivating example, let us say that we want to perform a deep copy of an array (described in :secref:`basic-array-handles:Deep Array Copies`).
However, we do not want to do the copy on a Kokkos device because we happen to know the data is not on that device and we do not want to spend the time to transfer the data to that device.
We can use a :class:`vtkm::cont::ScopedRuntimeDeviceTracker` to temporarily disable the Kokkos device for this operation.
.. todo:: Fix reference above.
.. load-example:: RestrictCopyDevice
:file: GuideExampleRuntimeDeviceTracker.cxx
:caption: Disabling a device with :class:`vtkm::cont::RuntimeDeviceTracker`.

@ -1,3 +1,11 @@
==============================
Developing Algorithms
==============================
.. toctree::
:maxdepth: 2
general-approach.rst
basic-array-handles.rst
simple-worklets.rst
basic-filter-impl.rst

@ -216,9 +216,7 @@ Slice
A slice operation intersects a mesh with a surface.
The :class:`vtkm::filter::contour::Slice` filter uses a :class:`vtkm::ImplicitFunctionGeneral` to specify an implicit surface to slice on.
A plane is a common thing to slice on, but other surfaces are available.
See Chapter \ref{chap:ImplicitFunctions} for information on implicit functions.
.. todo:: Fix reference to implicit functions above.
See :chapref:`implicit-functions:Implicit Functions` for information on implicit functions.
.. doxygenclass:: vtkm::filter::contour::Slice
:members:
@ -266,11 +264,9 @@ Clip with Implicit Function
double: clip; implicit function
The :class:`vtkm::filter::contour::ClipWithImplicitFunction` function takes an implicit function and removes all parts of the data that are inside (or outside) that function.
See Chapter \ref{chap:ImplicitFunctions} for more detail on how implicit functions are represented in |VTKm|.
See :chapref:`implicit-functions:Implicit Functions` for more detail on how implicit functions are represented in |VTKm|.
A companion filter that discards a region of the data based on the value of a scalar field is described in :secref:`provided-filters:Extract Geometry`.
.. todo:: Fix above reference to implicit function chapter.
The result of :class:`vtkm::filter::contour::ClipWithImplicitFunction` is a volume.
If a cell has its vertices positioned all outside the implicit function, then it will be discarded entirely.
Likewise, if a cell its vertices all inside the implicit function, then it will be retained in its entirety.
@ -383,12 +379,10 @@ Extract Geometry
double: extract geometry; filter
The :class:`vtkm::filter::entity_extraction::ExtractGeometry` filter extracts all of the cells in a :class:`vtkm::cont::DataSet` that is inside or outside of an implicit function.
Implicit functions are described in Chapter \ref{chap:ImplicitFunctions}.
Implicit functions are described in :chapref:`implicit-functions:Implicit Functions`.
They define a function in 3D space that follow a geometric shape.
The inside of the implicit function is the region of negative values.
.. todo:: Fix above reference to implicit function chapter.
.. doxygenclass:: vtkm::filter::entity_extraction::ExtractGeometry
:members:
@ -535,9 +529,7 @@ Field to Colors
The :class:`vtkm::filter::field_transform::FieldToColors` filter takes a field in a data set, looks up each value in a color table, and writes the resulting colors to a new field.
The color to be used for each field value is specified using a :class:`vtkm::cont::ColorTable` object.
:class:`vtkm::cont::ColorTable` objects are also used with |VTKm|'s rendering module and are described in Section~\ref{sec:ColorTables}.
.. todo:: Fix above reference to color tables section.
:class:`vtkm::cont::ColorTable` objects are also used with |VTKm|'s rendering module and are described in :secref:`rendering:Color Tables`.
:class:`vtkm::filter::field_transform::FieldToColors` has three modes it can use to select how it should treat the input field.
These input modes are contained in :enum:`vtkm::filter::field_transform::FieldToColors::InputMode`.

@ -55,14 +55,11 @@ The data is then read in by calling the :func:`vtkm::io::VTKDataSetReader::ReadD
:file: VTKmQuickStart.cxx
:caption: Reading data from a VTK legacy file.
.. todo:: Uncomment and cross reference.
The ``ReadDataSet`` method returns the data in a :class:`vtkm::cont::DataSet` object.
The structure and features of a ``DataSet`` object is described in :chapref:`dataset:Data Sets`.
For the purposes of this quick start, we will treat ``DataSet`` as a mostly opaque object that gets passed to and from operations in |VTKm|.
..
The ``ReadDataSet`` method returns the data in a :class:`vtkm::cont::DataSet` object.
The structure and features of a ``DataSet`` object is described in Chapter \ref{chap:DataSet}.
For the purposes of this quick start, we will treat ``DataSet`` as a mostly opaque object that gets passed to and from operations in |VTKm|.
More information about |VTKm|'s file readers and writers can be found in Chapter \ref{chap:FileIO}.
More information about |VTKm|'s file readers and writers can be found in :chapref:`io:File I/O`.
------------------------------
@ -75,18 +72,14 @@ Algorithms in |VTKm| are encapsulated in units called *filters*.
A filter takes in a ``DataSet``, processes it, and returns a new ``DataSet``.
The returned ``DataSet`` often, but not always, contains data inherited from the source data.
.. todo:: Fix cross reference to Running Filters.
|VTKm| comes with many filters, which are documented in Chapter \ref{chap:RunningFilters}.
|VTKm| comes with many filters, which are documented in :chapref:`provided-filters:Provided Filters`.
For this example, we will demonstrate the use of the :class:`vtkm::filter::MeshQuality` filter, which is defined in the :file:`vtkm/filter/MeshQuality.h` header file.
The ``MeshQuality`` filter will compute for each cell in the input data will compute a quantity representing some metric of the cell's shape.
Several metrics are available, and in this example we will find the area of each cell.
.. todo:: Fix cross reference to MeshQuality.
Like all filters, ``MeshQuality`` contains an ``Execute`` method that takes an input ``DataSet`` and produces an output ``DataSet``.
It also has several methods used to set up the parameters of the execution.
Section \ref{sec:MeshQuality} provides details on all the options of ``MeshQuality``.
:secref:`provided-filters:Mesh Quality Metrics` provides details on all the options of ``MeshQuality``.
Suffice it to say that in this example we instruct the filter to find the area of each cell, which it will output to a field named ``area``.
.. load-example:: VTKmQuickStartFilter
@ -103,11 +96,9 @@ Rendering an Image
Although it is possible to leverage external rendering systems, |VTKm| comes with its own self-contained image rendering algorithms.
These rendering classes are completely implemented with the parallel features provided by |VTKm|, so using rendering in |VTKm| does not require any complex library dependencies.
.. todo:: Fix cross reference to rendering chapter.
Even a simple rendering scene requires setting up several parameters to establish what is to be featured in the image including what data should be rendered, how that data should be represented, where objects should be placed in space, and the qualities of the image to generate.
Consequently, setting up rendering in |VTKm| involves many steps.
Chapter \ref{chap:Rendering} goes into much detail on the ways in which a rendering scene is specified.
:chapref:`rendering:Rendering` goes into much detail on the ways in which a rendering scene is specified.
For now, we just briefly present some boilerplate to achieve a simple rendering.
.. load-example:: VTKmQuickStartRender

@ -0,0 +1,195 @@
==============================
Simple Worklets
==============================
.. index:: worklet; creating
The simplest way to implement an algorithm in |VTKm| is to create a *worklet*.
A worklet is fundamentally a functor that operates on an element of data.
Thus, it is a ``class`` or ``struct`` that has an overloaded parenthesis operator (which must be declared ``const`` for thread safety).
However, worklets are also embedded with a significant amount of metadata on how the data should be managed and how the execution should be structured.
.. load-example:: SimpleWorklet
:file: GuideExampleSimpleAlgorithm.cxx
:caption: A simple worklet.
As can be seen in :numref:`ex:SimpleWorklet`, a worklet is created by implementing a ``class`` or ``struct`` with the following features.
.. index::
single: control signature
single: signature; control
single: execution signature
single: signature; execution
single: input domain
1. The class must publicly inherit from a base worklet class that specifies the type of operation being performed (:exlineref:`ex:SimpleWorklet:Inherit`).
2. The class must contain a functional type named ``ControlSignature`` (:exlineref:`ex:SimpleWorklet:ControlSignature`), which specifies what arguments are expected when invoking the class in the control environment.
3. The class must contain a functional type named ``ExecutionSignature`` (:exlineref:`ex:SimpleWorklet:ExecutionSignature`), which specifies how the data gets passed from the arguments in the control environment to the worklet running in the execution environment.
4. The class specifies an ``InputDomain`` (:exlineref:`ex:SimpleWorklet:InputDomain`), which identifies which input parameter defines the input domain of the data.
5. The class must contain an implementation of the parenthesis operator, which is the method that is executed in the execution environment (lines :exlineref:`{line}<ex:SimpleWorklet:OperatorStart>`--:exlineref:`{line}<ex:SimpleWorklet:OperatorEnd>`).
The parenthesis operator must be declared ``const``.
------------------------------
Control Signature
------------------------------
.. index::
single: control signature
single: signature; control
single: worklet; control signature
The control signature of a worklet is a functional type named ``ControlSignature``.
The function prototype matches what data are provided when the worklet is invoked (as described in :secref:`simple-worklets:Invoking a Worklet`).
.. load-example:: ControlSignature
:file: GuideExampleSimpleAlgorithm.cxx
:caption: A ``ControlSignature``.
.. didyouknow::
If the code in :numref:`ex:ControlSignature` looks strange, you may be unfamiliar with :index:`function types`.
In C++, functions have types just as variables and classes do.
A function with a prototype like
``void functionName(int arg1, float arg2);``
has the type ``void(int, float)``.
|VTKm| uses function types like this as a :index:`signature` that defines the structure of a function call.
.. index:: signature; tags
The return type of the function prototype is always ``void``.
The parameters of the function prototype are *tags* that identify the type of data that is expected to be passed to invoke.
``ControlSignature`` tags are defined by the worklet type and the various tags are documented more fully in Chapter \ref{chap:WorkletTypeReference}.
In the case of :numref:`ex:ControlSignature`, the two tags ``FieldIn`` and ``FieldOut`` represent input and output data, respectively.
.. todo:: Fix reference above.
.. index::
single: control signature
single: signature; control
By convention, ``ControlSignature`` tag names start with the base concept (e.g. ``Field`` or ``Topology``) followed by the domain (e.g. ``Point`` or ``Cell``) followed by ``In`` or ``Out``.
For example, ``FieldPointIn`` would specify values for a field on the points of a mesh that are used as input (read only).
Although they should be there in most cases, some tag names might leave out the domain or in/out parts if they are obvious or ambiguous.
------------------------------
Execution Signature
------------------------------
.. index::
single: execution signature
single: signature; execution
single: worklet; execution signature
Like the control signature, the execution signature of a worklet is a functional type named ``ExecutionSignature``.
The function prototype must match the parenthesis operator (described in :secref:`simple-worklets:Worklet Operator`) in terms of arity and argument semantics.
.. load-example:: ExecutionSignature
:file: GuideExampleSimpleAlgorithm.cxx
:caption: An ``ExecutionSignature``.
The arguments of the ``ExecutionSignature``'s function prototype are tags that define where the data come from.
The most common tags are an underscore followed by a number, such as ``_1``, ``_2``, etc.
These numbers refer back to the corresponding argument in the ``ControlSignature``.
For example, ``_1`` means data from the first control signature argument, ``_2`` means data from the second control signature argument, etc.
Unlike the control signature, the execution signature optionally can declare a return type if the parenthesis operator returns a value.
If this is the case, the return value should be one of the numeric tags (i.e. ``_1``, ``_2``, etc.)
to refer to one of the data structures of the control signature.
If the parenthesis operator does not return a value, then ``ExecutionSignature`` should declare the return type as ``void``.
In addition to the numeric tags, there are other execution signature tags to represent other types of data.
For example, the ``WorkIndex`` tag identifies the instance of the worklet invocation.
Each call to the worklet function will have a unique ``WorkIndex``.
Other such tags exist and are described in the following section on worklet types where appropriate.
------------------------------
Input Domain
------------------------------
.. index::
single: input domain
single: worklet; input domain
All worklets represent data parallel operations that are executed over independent elements in some domain.
The type of domain is inherent from the worklet type, but the size of the domain is dependent on the data being operated on.
A worklet identifies the argument specifying the domain with a type alias named ``InputDomain``.
The ``InputDomain`` must be aliased to one of the execution signature numeric tags (i.e. ``_1``, ``_2``, etc.).
By default, the ``InputDomain`` points to the first argument, but a worklet can override that to point to any argument.
.. load-example:: InputDomain
:file: GuideExampleSimpleAlgorithm.cxx
:caption: An ``InputDomain`` declaration.
Different types of worklets can have different types of domain.
For example a simple field map worklet has a ``FieldIn`` argument as its input domain, and the size of the input domain is taken from the size of the associated field array.
Likewise, a worklet that maps topology has a ``CellSetIn`` argument as its input domain, and the size of the input domain is taken from the cell set.
Specifying the ``InputDomain`` is optional.
If it is not specified, the first argument is assumed to be the input domain.
------------------------------
Worklet Operator
------------------------------
A worklet is fundamentally a functor that operates on an element of data.
Thus, the algorithm that the worklet represents is contained in or called from the parenthesis operator method.
.. load-example:: WorkletOperator
:file: GuideExampleSimpleAlgorithm.cxx
:caption: An overloaded parenthesis operator of a worklet.
There are some constraints on the parenthesis operator.
First, it must have the same arity as the ``ExecutionSignature``, and the types of the parameters and return must be compatible.
Second, because it runs in the execution environment, it must be declared with the ``VTKM_EXEC`` (or ``VTKM_EXEC_CONT``) modifier.
Third, the method must be declared ``const`` to help preserve thread safety.
------------------------------
Invoking a Worklet
------------------------------
.. index:: worklet; invoke
Previously in this chapter we discussed creating a simple worklet.
In this section we describe how to run the worklet in parallel.
A worklet is run using the :class:`vtkm::cont::Invoker` class.
.. load-example:: WorkletInvoke
:file: GuideExampleSimpleAlgorithm.cxx
:caption: Invoking a worklet.
Using an :class:`vtkm::cont::Invoker` is simple.
First, an :class:`vtkm::cont::Invoker` can be simply constructed with no arguments (:exlineref:`ex:WorkletInvoke:Construct`).
Next, the :class:`vtkm::cont::Invoker` is called as if it were a function (:exlineref:`ex:WorkletInvoke:Call`).
The first argument to the invoke is always an instance of the worklet.
The remaining arguments are data that are passed (indirectly) to the worklet.
Each of these arguments (after the worklet) match a corresponding argument listed in the ``ControlSignature``.
So in the invocation in :exlineref:`ex:WorkletInvoke:Call`, the second and third arguments correspond the the two ``ControlSignature`` arguments given in :numref:`ex:ControlSignature`.
``psiArray`` corresponds to the ``FieldIn`` argument and ``nmsArray`` corresponds to the ``FieldOut`` argument.
.. doxygenstruct:: vtkm::cont::Invoker
:members:
----------------------------------------
Preview of More Complex Worklets
----------------------------------------
This chapter demonstrates the creation of a worklet that performs a very simple math operation in parallel.
However, we have just scratched the surface of the kinds of algorithms that can be expressed with |VTKm| worklets.
There are many more execution patterns and data handling constructs.
The following example gives a preview of some of the more advanced features of worklets.
.. load-example:: ComplexWorklet
:file: GuideExampleCellEdgesFaces.cxx
:caption: A more complex worklet.
We will discuss the many features available in the worklet framework throughout :partref:`part-advanced:Advanced Development`.

@ -13,11 +13,13 @@
namespace vtkm
{
enum class CopyFlag
/// @brief Identifier used to specify whether a function should deep copy data.
enum struct CopyFlag
{
Off = 0,
On = 1
};
}
#endif // vtk_m_Flags_h

@ -608,15 +608,8 @@ private:
class VTKM_ALWAYS_EXPORT Plane : public vtkm::internal::ImplicitFunctionBase<Plane>
{
public:
/// Construct plane passing through origin and normal to z-axis.
VTKM_EXEC_CONT Plane()
: Origin(Scalar(0))
, Normal(Scalar(0), Scalar(0), Scalar(1))
{
}
/// Construct a plane through the origin with the given normal.
VTKM_EXEC_CONT explicit Plane(const Vector& normal)
VTKM_EXEC_CONT explicit Plane(const Vector& normal = { 0, 0, 1 })
: Origin(Scalar(0))
, Normal(normal)
{
@ -672,15 +665,8 @@ private:
class VTKM_ALWAYS_EXPORT Sphere : public vtkm::internal::ImplicitFunctionBase<Sphere>
{
public:
/// Construct sphere with center at (0,0,0) and radius = 0.5.
VTKM_EXEC_CONT Sphere()
: Radius(Scalar(0.5))
, Center(Scalar(0))
{
}
/// Construct a sphere with center at (0,0,0) and the given radius.
VTKM_EXEC_CONT explicit Sphere(Scalar radius)
VTKM_EXEC_CONT explicit Sphere(Scalar radius = 0.5)
: Radius(radius)
, Center(Scalar(0))
{

@ -116,8 +116,6 @@ void ArrayCopyImpl(const vtkm::cont::ArrayHandle<T, S>& source,
/// pay heed and look for a different way to copy the data (perhaps
/// using `ArrayCopyDevice`).
///
/// @{
///
template <typename SourceArrayType, typename DestArrayType>
inline void ArrayCopy(const SourceArrayType& source, DestArrayType& destination)
{
@ -128,6 +126,7 @@ inline void ArrayCopy(const SourceArrayType& source, DestArrayType& destination)
}
// Special case where we allow a const UnknownArrayHandle as output.
/// @copydoc ArrayCopy
template <typename SourceArrayType>
inline void ArrayCopy(const SourceArrayType& source, vtkm::cont::UnknownArrayHandle& destination)
{
@ -145,8 +144,6 @@ void ArrayCopy(const vtkm::cont::UnknownArrayHandle&, const vtkm::cont::ArrayHan
VTKM_STATIC_ASSERT_MSG(sizeof(T) == 0, "Copying to a constant ArrayHandle is not allowed.");
}
/// @}
/// \brief Copies from an unknown to a known array type.
///
/// Often times you have an array of an unknown type (likely from a data set),

@ -129,6 +129,11 @@ struct ArrayHandleCheck
{
};
/// @brief Checks that the given type is a `vtkm::cont::ArrayHandle`.
///
/// If the type is not a `vtkm::cont::ArrayHandle` or a subclass, a static assert will
/// cause a compile exception. This is a good way to ensure that a template argument
/// that is assumed to be an array handle type actually is.
#define VTKM_IS_ARRAY_HANDLE(T) VTKM_STATIC_ASSERT(::vtkm::cont::internal::ArrayHandleCheck<T>{})
} // namespace internal
@ -273,7 +278,7 @@ VTKM_CONT_EXPORT VTKM_CONT bool ArrayHandleIsOnDevice(
} // namespace detail
/// \brief Manages an array-worth of data.
/// @brief Manages an array-worth of data.
///
/// `ArrayHandle` manages as array of data that can be manipulated by VTKm
/// algorithms. The `ArrayHandle` may have up to two copies of the array, one
@ -337,7 +342,6 @@ public:
{
}
///@{
/// Special constructor for subclass specializations that need to set the
/// initial state array. Used when pulling data from other sources.
///
@ -346,11 +350,13 @@ public:
{
}
/// Special constructor for subclass specializations that need to set the
/// initial state array. Used when pulling data from other sources.
///
VTKM_CONT explicit ArrayHandle(std::vector<vtkm::cont::internal::Buffer>&& buffers) noexcept
: Buffers(std::move(buffers))
{
}
///@}
/// Destructs an empty ArrayHandle.
///
@ -361,7 +367,7 @@ public:
///
VTKM_CONT ~ArrayHandle() {}
/// \brief Copies an ArrayHandle
/// @brief Shallow copies an ArrayHandle
///
VTKM_CONT
vtkm::cont::ArrayHandle<ValueType, StorageTag>& operator=(
@ -371,7 +377,7 @@ public:
return *this;
}
/// \brief Move and Assignment of an ArrayHandle
/// @brief Move and Assignment of an ArrayHandle
///
VTKM_CONT
vtkm::cont::ArrayHandle<ValueType, StorageTag>& operator=(
@ -381,7 +387,7 @@ public:
return *this;
}
/// Like a pointer, two \c ArrayHandles are considered equal if they point
/// Like a pointer, two `ArrayHandle`s are considered equal if they point
/// to the same location in memory.
///
VTKM_CONT
@ -412,8 +418,7 @@ public:
///
VTKM_CONT StorageType GetStorage() const { return StorageType{}; }
///@{
/// \brief Get an array portal that can be used in the control environment.
/// @brief Get an array portal that can be used in the control environment.
///
/// The returned array can be used in the control environment to read values from the array. (It
/// is not possible to write to the returned portal. That is `Get` will work on the portal, but
@ -428,14 +433,14 @@ public:
vtkm::cont::Token token;
return this->ReadPortal(token);
}
/// @copydoc ReadPortalType
VTKM_CONT ReadPortalType ReadPortal(vtkm::cont::Token& token) const
{
return StorageType::CreateReadPortal(
this->GetBuffers(), vtkm::cont::DeviceAdapterTagUndefined{}, token);
}
///@}
/// \brief Get an array portal that can be used in the control environment.
/// @brief Get an array portal that can be used in the control environment.
///
/// The returned array can be used in the control environment to reand and write values to the
/// array.
@ -449,6 +454,7 @@ public:
vtkm::cont::Token token;
return this->WritePortal(token);
}
/// @copydoc WritePortal
VTKM_CONT WritePortalType WritePortal(vtkm::cont::Token& token) const
{
return StorageType::CreateWritePortal(
@ -468,16 +474,15 @@ public:
return StorageType::GetNumberOfComponentsFlat(this->GetBuffers());
}
///@{
/// \brief Allocates an array large enough to hold the given number of values.
/// @brief Allocates an array large enough to hold the given number of values.
///
/// The allocation may be done on an already existing array. If so, then the data
/// are preserved as best as possible if the preserve flag is set to `vtkm::CopyFlag::On`.
/// If the preserve flag is set to `vtkm::CopyFlag::Off` (the default), any existing data
/// could be wiped out.
///
/// This method can throw `ErrorBadAllocation` if the array cannot be allocated or
/// `ErrorBadValue` if the allocation is not feasible (for example, the
/// This method can throw `vtkm::cont::ErrorBadAllocation` if the array cannot be allocated or
/// `vtkm::cont::ErrorBadValue` if the allocation is not feasible (for example, the
/// array storage is read-only).
///
VTKM_CONT void Allocate(vtkm::Id numberOfValues,
@ -487,16 +492,15 @@ public:
StorageType::ResizeBuffers(numberOfValues, this->GetBuffers(), preserve, token);
}
/// @copydoc Allocate
VTKM_CONT void Allocate(vtkm::Id numberOfValues,
vtkm::CopyFlag preserve = vtkm::CopyFlag::Off) const
{
vtkm::cont::Token token;
this->Allocate(numberOfValues, preserve, token);
}
///@}
///@{
/// \brief Allocates an array and fills it with an initial value.
/// @brief Allocates an array and fills it with an initial value.
///
/// `AllocateAndFill` behaves similar to `Allocate` except that after allocation it fills
/// the array with a given `fillValue`. This method is convenient when you wish to initialize
@ -529,6 +533,7 @@ public:
}
}
/// @copydoc AllocateAndFill
VTKM_CONT void AllocateAndFill(vtkm::Id numberOfValues,
const ValueType& fillValue,
vtkm::CopyFlag preserve = vtkm::CopyFlag::Off) const
@ -536,10 +541,8 @@ public:
vtkm::cont::Token token;
this->AllocateAndFill(numberOfValues, fillValue, preserve, token);
}
///@}
/// @{
/// \brief Fills the array with a given value.
/// @brief Fills the array with a given value.
///
/// After calling this method, every entry in the array from `startIndex` to `endIndex`.
/// of the array is set to `fillValue`. If `startIndex` or `endIndex` is not specified,
@ -552,17 +555,18 @@ public:
{
StorageType::Fill(this->GetBuffers(), fillValue, startIndex, endIndex, token);
}
/// @copydoc Fill
VTKM_CONT void Fill(const ValueType& fillValue, vtkm::Id startIndex, vtkm::Id endIndex) const
{
vtkm::cont::Token token;
this->Fill(fillValue, startIndex, endIndex, token);
}
/// @copydoc Fill
VTKM_CONT void Fill(const ValueType& fillValue, vtkm::Id startIndex = 0) const
{
vtkm::cont::Token token;
this->Fill(fillValue, startIndex, this->GetNumberOfValues(), token);
}
/// @}
/// Releases any resources being used in the execution environment (that are
/// not being shared by the control environment).

@ -288,6 +288,8 @@ VTKM_CONT vtkm::cont::ArrayHandleBasic<T> make_ArrayHandleMove(std::vector<T, Al
internal::StdVectorReallocater<T, Allocator>);
}
/// Move an std::vector into an ArrayHandle.
///
template <typename T, typename Allocator>
VTKM_CONT vtkm::cont::ArrayHandleBasic<T> make_ArrayHandle(std::vector<T, Allocator>&& array,
vtkm::CopyFlag vtkmNotUsed(copy))