vtk-m/docs/changelog/tuple.md
2020-03-16 17:12:17 -06:00

6.7 KiB

Add a vtkm::Tuple class

This change added a vtkm::Tuple class that is very similar in nature to std::tuple. This should replace our use of tao tuple.

The motivation for this change was some recent attempts at removing objects like Invocation and FunctionInterface. I expected these changes to speed up the build, but in fact they ended up slowing down the build. I believe the problem was that these required packing variable parameters into a tuple. I was using the tao tuple class, but it seemed to slow down the compile. (That is, compiling tao's tuple seemed much slower than compiling the equivalent FunctionInterface class.)

The implementation of vtkm::Tuple is using pyexpander to build lots of simple template cases for the object (with a backup implementation for even longer argument lists). I believe the compiler is better and parsing through thousands of lines of simple templates than to employ clever MPL to build general templates.

Usage

The vtkm::Tuple class is defined in the vtkm::Tuple.h header file. A Tuple is designed to behave much like a std::tuple with some minor syntax differences to fit VTK-m coding standards.

A tuple is declared with a list of template argument types.

vtkm::Tuple<vtkm::Id, vtkm::Vec3f, vtkm::cont::ArrayHandle<vtkm::Float32>> myTuple;

If given no arguments, a vtkm::Tuple will default-construct its contained objects. A vtkm::Tuple can also be constructed with the initial values of all contained objects.

vtkm::Tuple<vtkm::Id, vtkm::Vec3f, vtkm::cont::ArrayHandle<vtkm::Float32>> 
  myTuple(0, vtkm::Vec3f(0, 1, 2), array);

For convenience there is a vtkm::MakeTuple function that takes arguments and packs them into a Tuple of the appropriate type. (There is also a vtkm::make_tuple alias to the function to match the std version.)

auto myTuple = vtkm::MakeTuple(0, vtkm::Vec3f(0, 1, 2), array);

Data is retrieved from a Tuple by using the vtkm::Get method. The Get method is templated on the index to get the value from. The index is of type vtkm::IdComponent. (There is also a vtkm::get that uses a std::size_t as the index type as an alias to the function to match the std version.)

vtkm::Id a = vtkm::Get<0>(myTuple);
vtkm::Vec3f b = vtkm::Get<1>(myTuple);
vtkm::cont::ArrayHandle<vtkm::Float32> c = vtkm::Get<2>(myTuple);

Likewise vtkm::TupleSize and vtkm::TupleElement (and their aliases vtkm::Tuple_size, vtkm::tuple_element, and vtkm::tuple_element_t) are provided.

Extended Functionality

The vtkm::Tuple class contains some functionality beyond that of std::tuple to cover some common use cases in VTK-m that are tricky to implement. In particular, these methods allow you to use a Tuple as you would commonly use parameter packs. This allows you to stash parameter packs in a Tuple and then get them back out again.

For Each

vtkm::Tuple::ForEach() is a method that takes a function or functor and calls it for each of the items in the tuple. Nothing is returned from ForEach and any return value from the function is ignored.

ForEach can be used to check the validity of each item.

void CheckPositive(vtkm::Float64 x)
{
  if (x < 0)
  {
    throw vtkm::cont::ErrorBadValue("Values need to be positive.");
  }
}

// ...

  vtkm::Tuple<vtkm::Float64, vtkm::Float64, vtkm::Float64> tuple(
    CreateValue1(), CreateValue2(), CreateValue3());

  // Will throw an error if any of the values are negative.
  tuple.ForEach(CheckPositive);

ForEach can also be used to aggregate values.

struct SumFunctor
{
  vtkm::Float64 Sum = 0;
  
  template <typename T>
  void operator()(const T& x)
  {
    this->Sum = this->Sum + static_cast<vtkm::Float64>(x);
  }
};

// ...

  vtkm::Tuple<vtkm::Float32, vtkm::Float64, vtkm::Id> tuple(
    CreateValue1(), CreateValue2(), CreateValue3());

  SumFunctor sum;
  tuple.ForEach(sum);
  vtkm::Float64 average = sum.Sum / 3;

Transform

vtkm::Tuple::Transform is a method that builds a new Tuple by calling a function or functor on each of the items. The return value is placed in the corresponding part of the resulting Tuple, and the type is automatically created from the return type of the function.

struct GetReadPortalFunctor
{
  template <typename Array>
  typename Array::ReadPortal operator()(const Array& array) const
  {
    VTKM_IS_ARRAY_HANDLE(Array);
	return array.ReadPortal();
  }
};

// ...

  auto arrayTuple = vtkm::MakeTuple(array1, array2, array3);
  
  auto portalTuple = arrayTuple.Transform(GetReadPortalFunctor{});

Apply

vtkm::Tuple::Apply is a method that calls a function or functor using the objects in the Tuple as the arguments. If the function returns a value, that value is returned from Apply.

struct AddArraysFunctor
{
  template <typename Array1, typename Array2, typename Array3>
  vtkm::Id operator()(Array1 inArray1, Array2 inArray2, Array3 outArray) const
  {
    VTKM_IS_ARRAY_HANDLE(Array1);
    VTKM_IS_ARRAY_HANDLE(Array2);
    VTKM_IS_ARRAY_HANDLE(Array3);

    vtkm::Id length = inArray1.GetNumberOfValues();
	VTKM_ASSERT(inArray2.GetNumberOfValues() == length);
	outArray.Allocate(length);
	
	auto inPortal1 = inArray1.ReadPortal();
	auto inPortal2 = inArray2.ReadPortal();
	auto outPortal = outArray.WritePortal();
	for (vtkm::Id index = 0; index < length; ++index)
	{
	  outPortal.Set(index, inPortal1.Get(index) + inPortal2.Get(index));
	}
	
	return length;
  }
};

// ...

  auto arrayTuple = vtkm::MakeTuple(array1, array2, array3);

  vtkm::Id arrayLength = arrayTuple.Apply(AddArraysFunctor{});

If additional arguments are given to Apply, they are also passed to the function (before the objects in the Tuple). This is helpful for passing state to the function. (This feature is not available in either ForEach or Transform for technical implementation reasons.)

struct ScanArrayLengthFunctor
{
  template <std::size_t N, typename Array, typename... Remaining>
  std::array<vtkm::Id, N + 1 + sizeof...(Remaining)>
  operator()(const std::array<vtkm::Id, N>& partialResult,
             const Array& nextArray,
			 const Remaining&... remainingArrays) const
  {
    std::array<vtkm::Id, N + 1> nextResult;
	std::copy(partialResult.begin(), partialResult.end(), nextResult.begin());
    nextResult[N] = nextResult[N - 1] + nextArray.GetNumberOfValues();
	return (*this)(nextResult, remainingArray);
  }
  
  template <std::size_t N>
  std::array<vtkm::Id, N> operator()(const std::array<vtkm::Id, N>& result) const
  {
    return result;
  }
};

// ...

  auto arrayTuple = vtkm::MakeTuple(array1, array2, array3);
  
  std::array<vtkm::Id, 4> = 
    arrayTuple.Apply(ScanArrayLengthFunctor{}, std::array<vtkm::Id, 1>{ 0 });