4.10. Global Arrays and Topology
When writing an algorithm in VTK‑m by creating a worklet, the data each instance of the worklet has access to is intentionally limited. This allows VTK‑m to provide safety from race conditions and other parallel programming difficulties. However, there are times when the complexity of an algorithm requires all threads to have shared global access to a global structure. This chapter describes worklet tags that can be used to pass data globally to all instances of a worklet.
4.10.1. Whole Arrays
A whole array argument to a worklet allows you to pass in a vtkm::cont::ArrayHandle.
All instances of the worklet will have access to all the data in the vtkm::cont::ArrayHandle.
Common Errors
The VTK‑m worklet invoking mechanism performs many safety checks to prevent race conditions across concurrently running worklets. Using a whole array within a worklet circumvents this guarantee of safety, so be careful when using whole arrays, especially when writing to whole arrays.
A whole array is declared by adding a WholeArrayIn, a WholeArrayInOut, or a WholeArrayOut to the controlsignature of a worklet.
The corresponding argument to the vtkm::cont::Invoker should be an vtkm::cont::ArrayHandle.
The vtkm::cont::ArrayHandle must already be allocated in all cases, including when using WholeArrayOut.
When the data are passed to the operator of the worklet, it is passed as an array portal object.
(Array portals are discussed in Section 3.2.4 (Array Portals).)
This means that the worklet can access any entry in the array with ArrayPortal::Get() and/or ArrayPortal::Set() methods.
We have already seen a demonstration of using a whole array in Example 4.43 in Chapter 4.3 (Worklet Types) to perform a simple array copy. Here we will construct a more thorough example of building functionality that requires random array access.
Let’s say we want to measure the quality of triangles in a mesh. A common method for doing this is using the equation
where \(a\) is the area of the triangle and \(h_1\), \(h_2\), and \(h_3\) are the lengths of the sides. We can easily compute this in a cell to point map, but what if we want to speed up the computations by reducing precision? After all, we probably only care if the triangle is good, reasonable, or bad. So instead, let’s build a lookup table and then retrieve the triangle quality from that lookup table based on its sides.
The following example demonstrates creating such a table lookup in an array and using a worklet argument tagged with WholeArrayIn to make it accessible.
1namespace detail
2{
3
4static const vtkm::Id TRIANGLE_QUALITY_TABLE_DIMENSION = 8;
5static const vtkm::Id TRIANGLE_QUALITY_TABLE_SIZE =
6 TRIANGLE_QUALITY_TABLE_DIMENSION * TRIANGLE_QUALITY_TABLE_DIMENSION;
7
8VTKM_CONT
9vtkm::cont::ArrayHandle<vtkm::Float32> GetTriangleQualityTable()
10{
11 // Use these precomputed values for the array. A real application would
12 // probably use a larger array, but we are keeping it small for demonstration
13 // purposes.
14 static vtkm::Float32 triangleQualityBuffer[TRIANGLE_QUALITY_TABLE_SIZE] = {
15 0, 0, 0, 0, 0, 0, 0, 0,
16 0, 0, 0, 0, 0, 0, 0, 0.24431f,
17 0, 0, 0, 0, 0, 0, 0.43298f, 0.47059f,
18 0, 0, 0, 0, 0, 0.54217f, 0.65923f, 0.66408f,
19 0, 0, 0, 0, 0.57972f, 0.75425f, 0.82154f, 0.81536f,
20 0, 0, 0, 0.54217f, 0.75425f, 0.87460f, 0.92567f, 0.92071f,
21 0, 0, 0.43298f, 0.65923f, 0.82154f, 0.92567f, 0.97664f, 0.98100f,
22 0, 0.24431f, 0.47059f, 0.66408f, 0.81536f, 0.92071f, 0.98100f, 1
23 };
24
25 return vtkm::cont::make_ArrayHandle(
26 triangleQualityBuffer, TRIANGLE_QUALITY_TABLE_SIZE, vtkm::CopyFlag::Off);
27}
28
29template<typename T>
30VTKM_EXEC_CONT vtkm::Vec<T, 3> TriangleEdgeLengths(const vtkm::Vec<T, 3>& point1,
31 const vtkm::Vec<T, 3>& point2,
32 const vtkm::Vec<T, 3>& point3)
33{
34 return vtkm::make_Vec(vtkm::Magnitude(point1 - point2),
35 vtkm::Magnitude(point2 - point3),
36 vtkm::Magnitude(point3 - point1));
37}
38
39VTKM_SUPPRESS_EXEC_WARNINGS
40template<typename PortalType, typename T>
41VTKM_EXEC_CONT static vtkm::Float32 LookupTriangleQuality(
42 const PortalType& triangleQualityPortal,
43 const vtkm::Vec<T, 3>& point1,
44 const vtkm::Vec<T, 3>& point2,
45 const vtkm::Vec<T, 3>& point3)
46{
47 vtkm::Vec<T, 3> edgeLengths = TriangleEdgeLengths(point1, point2, point3);
48
49 // To reduce the size of the table, we just store the quality of triangles
50 // with the longest edge of size 1. The table is 2D indexed by the length
51 // of the other two edges. Thus, to use the table we have to identify the
52 // longest edge and scale appropriately.
53 T smallEdge1 = vtkm::Min(edgeLengths[0], edgeLengths[1]);
54 T tmpEdge = vtkm::Max(edgeLengths[0], edgeLengths[1]);
55 T smallEdge2 = vtkm::Min(edgeLengths[2], tmpEdge);
56 T largeEdge = vtkm::Max(edgeLengths[2], tmpEdge);
57
58 smallEdge1 /= largeEdge;
59 smallEdge2 /= largeEdge;
60
61 // Find index into array.
62 vtkm::Id index1 = static_cast<vtkm::Id>(
63 vtkm::Floor(smallEdge1 * (TRIANGLE_QUALITY_TABLE_DIMENSION - 1) + 0.5));
64 vtkm::Id index2 = static_cast<vtkm::Id>(
65 vtkm::Floor(smallEdge2 * (TRIANGLE_QUALITY_TABLE_DIMENSION - 1) + 0.5));
66 vtkm::Id totalIndex = index1 + index2 * TRIANGLE_QUALITY_TABLE_DIMENSION;
67
68 return triangleQualityPortal.Get(totalIndex);
69}
70
71} // namespace detail
72
73struct TriangleQualityWorklet : vtkm::worklet::WorkletVisitCellsWithPoints
74{
75 using ControlSignature = void(CellSetIn cells,
76 FieldInPoint pointCoordinates,
77 WholeArrayIn triangleQualityTable,
78 FieldOutCell triangleQuality);
79 using ExecutionSignature = _4(CellShape, _2, _3);
80 using InputDomain = _1;
81
82 template<typename CellShape,
83 typename PointCoordinatesType,
84 typename TriangleQualityTablePortalType>
85 VTKM_EXEC vtkm::Float32 operator()(
86 CellShape shape,
87 const PointCoordinatesType& pointCoordinates,
88 const TriangleQualityTablePortalType& triangleQualityTable) const
89 {
90 if (shape.Id != vtkm::CELL_SHAPE_TRIANGLE)
91 {
92 this->RaiseError("Only triangles are supported for triangle quality.");
93 return vtkm::Nan32();
94 }
95 else
96 {
97 return detail::LookupTriangleQuality(triangleQualityTable,
98 pointCoordinates[0],
99 pointCoordinates[1],
100 pointCoordinates[2]);
101 }
102 }
103};
104
105//
106// Later in the associated Filter class...
107//
108
109 vtkm::cont::ArrayHandle<vtkm::Float32> triangleQualityTable =
110 detail::GetTriangleQualityTable();
111
112 vtkm::cont::ArrayHandle<vtkm::Float32> triangleQualities;
113
114 this->Invoke(TriangleQualityWorklet{},
115 inputDataSet.GetCellSet(),
116 inputPointCoordinatesField,
117 triangleQualityTable,
118 triangleQualities);
4.10.2. Atomic Arrays
One of the problems with writing to whole arrays is that it is difficult to coordinate the access to an array from multiple threads. If multiple threads are going to write to a common index of an array, then you will probably need to use an atomic array.
An atomic array allows random access into an array of data, similar to a whole array. However, the operations on the values in the atomic array allow you to perform an operation that modifies its value that is guaranteed complete without being interrupted and potentially corrupted.
Common Errors
Due to limitations in available atomic operations, atomic arrays can currently only contain vtkm::Int32 or vtkm::Int64 values.
To use an array as an atomic array, first add the AtomicArrayInOut tag to the worklet’s ControlSignature.
The corresponding argument to the vtkm::cont::Invoker should be an vtkm::cont::ArrayHandle, which must already be allocated and initialized with values.
When the data are passed to the operator of the worklet, it is passed in a vtkmexec{AtomicArrayExecutionObject} structure.
-
template<typename T>
class AtomicArrayExecutionObject An object passed to a worklet when accessing an atomic array.
This object is created for the worklet when a
ControlSignatureargument isAtomicArrayInOut.AtomicArrayExecutionObjectbehaves similar to a normalArrayPortal. It has similarGet()andSet()methods, but it has additional features that allow atomic access as well as more methods for atomic operations.Public Functions
-
inline ValueType Get(vtkm::Id index, vtkm::MemoryOrder order = vtkm::MemoryOrder::Acquire) const
Perform an atomic load of the indexed element with acquire memory ordering.
- Parameters:
index – The index of the element to load.
order – The memory ordering to use for the load operation.
- Returns:
The value of the atomic array at index.
-
inline ValueType Add(vtkm::Id index, const ValueType &value, vtkm::MemoryOrder order = vtkm::MemoryOrder::SequentiallyConsistent) const
Peform an atomic addition with sequentially consistent memory ordering.
Warning
Overflow behavior from this operation is undefined.
- Parameters:
index – The index of the array element that will be added to.
value – The addend of the atomic add operation.
order – The memory ordering to use for the add operation.
- Returns:
The original value of the element at index (before addition).
-
inline void Set(vtkm::Id index, const ValueType &value, vtkm::MemoryOrder order = vtkm::MemoryOrder::Release) const
Peform an atomic store to memory while enforcing, at minimum, “release” memory ordering.
Warning
Using something like:
Should not be done as it is not thread safe, instead you should use the provided Add method instead.Set(index, Get(index)+N)
- Parameters:
index – The index of the array element that will be added to.
value – The value to write for the atomic store operation.
order – The memory ordering to use for the store operation.
-
inline bool CompareExchange(vtkm::Id index, ValueType *oldValue, const ValueType &newValue, vtkm::MemoryOrder order = vtkm::MemoryOrder::SequentiallyConsistent) const
Perform an atomic compare and exchange operation with sequentially consistent memory ordering.
This operation is typically used in a loop. For example usage, an atomic multiplication may be implemented using compare-exchange as follows:
AtomicArrayExecutionObject<vtkm::Int32> atomicArray = ...; // Compare-exchange multiplication: vtkm::Int32 current = atomicArray.Get(idx); // Load the current value at idx vtkm::Int32 newVal; do { newVal = current * multFactor; // the actual multiplication } while (!atomicArray.CompareExchange(idx, ¤t, newVal));
The
whilecondition here updatesnewValwhat the proper multiplication is given the expected current value. It then compares this to the value in the array. If the values match, the operation was successful and the loop exits. If the values do not match, the value atidxwas changed by another thread since the initial Get, and the compare-exchange operation failed — the target element was not modified by the compare-exchange call. If this happens, the loop body re-executes using the new value ofcurrentand tries again until it succeeds.Note that for demonstration purposes, the previous code is unnecessarily verbose. We can express the same atomic operation more succinctly with just two lines where
newValis just computed in place.vtkm::Int32 current = atomicArray.Get(idx); // Load the current value at idx while (!atomicArray.CompareExchange(idx, ¤t, current * multFactor));
- Parameters:
index – The index of the array element that will be atomically modified.
oldValue – A pointer to the expected value of the indexed element.
newValue – The value to replace the indexed element with.
order – The memory ordering to use for the compare and exchange operation.
- Returns:
If the operation is successful,
trueis returned. Otherwise,oldValueis replaced with the current value of the indexed element, the element is not modified, andfalseis returned. In either case,oldValuebecomes the value that was originally in the indexed element.
-
inline ValueType Get(vtkm::Id index, vtkm::MemoryOrder order = vtkm::MemoryOrder::Acquire) const
Common Errors
Atomic arrays help resolve hazards in parallel algorithms, but they come at a cost. Atomic operations are more costly than non-thread-safe ones, and they can slow a parallel program immensely if used incorrectly.
The following example uses an atomic array to count the bins in a histogram. It does this by making the array of histogram bins an atomic array and then using an atomic add. Note that this is not the fastest way to create a histogram. We gave an implementation in Section 4.3.4 (Reduce by Key) that is generally faster (unless your histogram happens to be very sparse). VTK‑m also comes with a histogram worklet that uses a similar approach.
1 struct CountBins : vtkm::worklet::WorkletMapField
2 {
3 using ControlSignature = void(FieldIn data, AtomicArrayInOut histogramBins);
4 using ExecutionSignature = void(_1, _2);
5 using InputDomain = _1;
6
7 vtkm::Range HistogramRange;
8 vtkm::Id NumberOfBins;
9
10 VTKM_CONT
11 CountBins(const vtkm::Range& histogramRange, vtkm::Id& numBins)
12 : HistogramRange(histogramRange)
13 , NumberOfBins(numBins)
14 {
15 }
16
17 template<typename T, typename AtomicArrayType>
18 VTKM_EXEC void operator()(T value, const AtomicArrayType& histogramBins) const
19 {
20 vtkm::Float64 interp =
21 (value - this->HistogramRange.Min) / this->HistogramRange.Length();
22 vtkm::Id bin = static_cast<vtkm::Id>(interp * this->NumberOfBins);
23 if (bin < 0)
24 {
25 bin = 0;
26 }
27 if (bin >= this->NumberOfBins)
28 {
29 bin = this->NumberOfBins - 1;
30 }
31
32 histogramBins.Add(bin, 1);
33 }
34 };
4.10.3. Whole Cell Sets
Section 4.3.2 (Topology Map) describes how to make a topology map filter that performs an operation on cell sets. The worklet has access to a single cell element (such as point or cell) and its immediate connections. But there are cases when you need more general queries on a topology. For example, you might need more detailed information than the topology map gives or you might need to trace connections from one cell to the next. To do this VTK‑m allows you to provide a whole cell set argument to a worklet that provides random access to the entire topology.
A whole cell set is declared by adding a WholeCellSetIn to the worklet’s ControlSignature.
The corresponding argument to the vtkm::cont::Invoker should be a vtkm::cont::CellSet subclass or an vtkm::cont::UnknownCellSet (both of which are described in Section 2.4.2 (Cell Sets)).
The WholeCellSetIn is templated and takes two arguments: the “visit” topology type and the “incident” topology type, respectively.
These template arguments must be one of the topology element tags, but for convenience you can use Point and Cell in lieu of vtkm::TopologyElementTagPoint and vtkm::TopologyElementTagCell, respectively.
The “visit” and “incident” topology types define which topological elements can be queried (visited) and which incident elements are returned.
The semantics of the “visit” and “incident” topology is the same as that for the general topology maps described in Section 4.3.2 (Topology Map).
You can look up an element of the “visit” topology by index and then get all of the “incident” elements from it.
For example, a WholeCellSetIn<Cell, Point> allows you to find all the points that are incident on each cell (as well as querying the cell shape). Likewise, a WholeCellSetIn<Point, Cell> allows you to find all the cells that are incident on each point.
The default parameters of WholeCellSetIn are visiting cells with incident points.
That is, WholeCellSetIn<> is equivalent to WholeCellSetIn<Cell, Point>.
When the cell set is passed to the operator of the worklet, it is passed in a special connectivity object.
The actual object type depends on the cell set, but vtkm::exec::ConnectivityExplicit and are two common examples vtkm::exec::ConnectivityStructured.
-
template<typename ShapesPortalType, typename ConnectivityPortalType, typename OffsetsPortalType>
class ConnectivityExplicit A class holding information about topology connections.
An object of
ConnectivityExplicitis provided to a worklet when theControlSignatureargument isWholeCellSetInand thevtkm::cont::CellSetprovided is avtkm::cont::CellSetExplicit.Public Types
-
using CellShapeTag = vtkm::CellShapeTagGeneric
The tag representing the cell shape of the visited elements.
The tag type is allways
vtkm::CellShapeTagGenericand its id is filled with the identifier for the appropriate shape.
-
using IndicesType = vtkm::VecFromPortal<ConnectivityPortalType>
Type of variable that lists of incident indices will be put into.
Public Functions
-
inline SchedulingRangeType GetNumberOfElements() const
Provides the number of elements in the topology.
This number of elements is associated with the “visit” type of topology element, which is the first template argument to
WholeCellSetIn. The number of elements defines the valid indices for the other methods of this class.
-
inline CellShapeTag GetCellShape(vtkm::Id index) const
Returns a tagfor the cell shape associated with the element at the given index.
The tag type is allways
vtkm::CellShapeTagGenericand its id is filled with the identifier for the appropriate shape.
-
inline vtkm::IdComponent GetNumberOfIndices(vtkm::Id index) const
Given the index of a visited element, returns the number of incident elements touching it.
-
inline IndicesType GetIndices(vtkm::Id index) const
Provides the indices of all elements incident to the visit element of the provided index.
Returns a Vec-like object containing the indices for the given index. The object returned is not an actual array, but rather an object that loads the indices lazily out of the connectivity array. This prevents us from having to know the number of indices at compile time.
-
using CellShapeTag = vtkm::CellShapeTagGeneric
-
template<typename VisitTopology, typename IncidentTopology, vtkm::IdComponent Dimension>
class ConnectivityStructured A class holding information about topology connections.
An object of
ConnectivityStructuredis provided to a worklet when theControlSignatureargument isWholeCellSetInand thevtkm::cont::CellSetprovided is avtkm::cont::CellSetStructured.Public Types
-
using CellShapeTag = typename Helper::CellShapeTag
The tag representing the cell shape of the visited elements.
If the “visit” element is cells, then the returned tag is
vtkm::CellShapeTagHexahedronfor a 3D structured grid,vtkm::CellShapeTagQuadfor a 2D structured grid, orvtkm::CellShapeLinefor a 1D structured grid.
-
using IndicesType = typename Helper::IndicesType
Type of variable that lists of incident indices will be put into.
Public Functions
-
inline vtkm::Id GetNumberOfElements() const
Provides the number of elements in the topology.
This number of elements is associated with the “visit” type of topology element, which is the first template argument to
WholeCellSetIn. The number of elements defines the valid indices for the other methods of this class.
-
inline CellShapeTag GetCellShape(vtkm::Id) const
Returns a tag for the cell shape associated with the element at the given index.
If the “visit” element is cells, then the returned tag is
vtkm::CellShapeTagHexahedronfor a 3D structured grid,vtkm::CellShapeTagQuadfor a 2D structured grid, orvtkm::CellShapeLinefor a 1D structured grid.
-
template<typename IndexType>
inline vtkm::IdComponent GetNumberOfIndices(const IndexType &index) const Given the index of a visited element, returns the number of incident elements touching it.
-
template<typename IndexType>
inline IndicesType GetIndices(const IndexType &index) const Provides the indices of all elements incident to the visit element of the provided index.
-
inline SchedulingRangeType FlatToLogicalVisitIndex(vtkm::Id flatVisitIndex) const
Convenience method that converts a flat, 1D index to the visited elements to a
vtkm::Veccontaining the logical indices in the grid.
-
inline SchedulingRangeType FlatToLogicalIncidentIndex(vtkm::Id flatIncidentIndex) const
Convenience method that converts a flat, 1D index to the incident elements to a
vtkm::Veccontaining the logical indices in the grid.
-
inline vtkm::Id LogicalToFlatVisitIndex(const SchedulingRangeType &logicalVisitIndex) const
Convenience method that converts logical indices in a
vtkm::Vecof a visited element to a flat, 1D index.
-
inline vtkm::Id LogicalToFlatIncidentIndex(const SchedulingRangeType &logicalIncidentIndex) const
Convenience method that converts logical indices in a
vtkm::Vecof an incident element to a flat, 1D index.
-
using CellShapeTag = typename Helper::CellShapeTag
All these connectivity objects share a common interface.
In particular, the share the types CellShapeTag and IndicesType.
They also share the methods GetNumberOfElements(), GetCellShape(), GetNumberOfIndices(), and GetIndices().
VTK‑m comes with several functions to work with the shape and index information returned from these connectivity objects. Most of these methods are documented in Chapter 4.7 (Working with Cells).
Let us use the whole cell set feature to help us determine the “flatness” of a polygonal mesh. We will do this by summing up all the angles incident on each on each point. That is, for each point, we will find each incident polygon, then find the part of that polygon using the given point, then computing the angle at that point, and then summing for all such angles. So, for example, in the mesh fragment shown in Figure 4.4 one of the angles attached to the middle point is labeled \(\theta_{j}\).
Figure 4.4 The angles incident around a point in a mesh.
We want a worklet to compute \(\sum_{j} \theta\) for all such attached angles. This measure is related (but not the same as) the curvature of the surface. A flat surface will have a sum of \(2\pi\). Convex and concave surfaces have a value less than \(2\pi\), and saddle surfaces have a value greater than \(2\pi\).
To do this, we create a visit points with cells worklet (Section 4.3.2.2 (Visit Points with Cells)) that visits every point and gives the index of every incident cell. The worklet then uses a whole cell set to inspect each incident cell to measure the attached angle and sum them together.
1struct SumOfAngles : vtkm::worklet::WorkletVisitPointsWithCells
2{
3 using ControlSignature = void(CellSetIn inputCells,
4 WholeCellSetIn<>, // Same as inputCells
5 WholeArrayIn pointCoords,
6 FieldOutPoint angleSum);
7 using ExecutionSignature = void(CellIndices incidentCells,
8 InputIndex pointIndex,
9 _2 cellSet,
10 _3 pointCoordsPortal,
11 _4 outSum);
12 using InputDomain = _1;
13
14 template<typename IncidentCellVecType,
15 typename CellSetType,
16 typename PointCoordsPortalType,
17 typename SumType>
18 VTKM_EXEC void operator()(const IncidentCellVecType& incidentCells,
19 vtkm::Id pointIndex,
20 const CellSetType& cellSet,
21 const PointCoordsPortalType& pointCoordsPortal,
22 SumType& outSum) const
23 {
24 using CoordType = typename PointCoordsPortalType::ValueType;
25
26 CoordType thisPoint = pointCoordsPortal.Get(pointIndex);
27
28 outSum = 0;
29 for (vtkm::IdComponent incidentCellIndex = 0;
30 incidentCellIndex < incidentCells.GetNumberOfComponents();
31 ++incidentCellIndex)
32 {
33 // Get information about incident cell.
34 vtkm::Id cellIndex = incidentCells[incidentCellIndex];
35 typename CellSetType::CellShapeTag cellShape = cellSet.GetCellShape(cellIndex);
36 typename CellSetType::IndicesType cellConnections = cellSet.GetIndices(cellIndex);
37 vtkm::IdComponent numPointsInCell = cellSet.GetNumberOfIndices(cellIndex);
38 vtkm::IdComponent numEdges;
39 vtkm::exec::CellEdgeNumberOfEdges(numPointsInCell, cellShape, numEdges);
40
41 // Iterate over all edges and find the first one with pointIndex.
42 // Use that to find the first vector.
43 vtkm::IdComponent edgeIndex = -1;
44 CoordType vec1;
45 while (true)
46 {
47 ++edgeIndex;
48 if (edgeIndex >= numEdges)
49 {
50 this->RaiseError("Bad cell. Could not find two incident edges.");
51 return;
52 }
53 vtkm::IdComponent2 edge;
54 vtkm::exec::CellEdgeLocalIndex(
55 numPointsInCell, 0, edgeIndex, cellShape, edge[0]);
56 vtkm::exec::CellEdgeLocalIndex(
57 numPointsInCell, 1, edgeIndex, cellShape, edge[1]);
58 if (cellConnections[edge[0]] == pointIndex)
59 {
60 vec1 = pointCoordsPortal.Get(cellConnections[edge[1]]) - thisPoint;
61 break;
62 }
63 else if (cellConnections[edge[1]] == pointIndex)
64 {
65 vec1 = pointCoordsPortal.Get(cellConnections[edge[0]]) - thisPoint;
66 break;
67 }
68 else
69 {
70 // Continue to next iteration of loop.
71 }
72 }
73
74 // Continue iteration over remaining edges and find the second one with
75 // pointIndex. Use that to find the second vector.
76 CoordType vec2;
77 while (true)
78 {
79 ++edgeIndex;
80 if (edgeIndex >= numEdges)
81 {
82 this->RaiseError("Bad cell. Could not find two incident edges.");
83 return;
84 }
85 vtkm::IdComponent2 edge;
86 vtkm::exec::CellEdgeLocalIndex(
87 numPointsInCell, 0, edgeIndex, cellShape, edge[0]);
88 vtkm::exec::CellEdgeLocalIndex(
89 numPointsInCell, 1, edgeIndex, cellShape, edge[1]);
90 if (cellConnections[edge[0]] == pointIndex)
91 {
92 vec2 = pointCoordsPortal.Get(cellConnections[edge[1]]) - thisPoint;
93 break;
94 }
95 else if (cellConnections[edge[1]] == pointIndex)
96 {
97 vec2 = pointCoordsPortal.Get(cellConnections[edge[0]]) - thisPoint;
98 break;
99 }
100 else
101 {
102 // Continue to next iteration of loop.
103 }
104 }
105
106 // The dot product of two unit vectors is equal to the cosine of the
107 // angle between them.
108 vtkm::Normalize(vec1);
109 vtkm::Normalize(vec2);
110 SumType cosine = static_cast<SumType>(vtkm::Dot(vec1, vec2));
111
112 outSum += vtkm::ACos(cosine);
113 }
114 }
115};