//============================================================================ // 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. // // Copyright 2014 Sandia Corporation. // Copyright 2014 UT-Battelle, LLC. // Copyright 2014 Los Alamos National Security. // // Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, // the U.S. Government retains certain rights in this software. // // Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National // Laboratory (LANL), the U.S. Government retains certain rights in // this software. //============================================================================ #ifndef vtkm_m_worklet_Clip_h #define vtkm_m_worklet_Clip_h #include #include #include #include #include #include #include #include #include #include #include #include #if defined(THRUST_MAJOR_VERSION) && THRUST_MAJOR_VERSION == 1 && \ THRUST_MINOR_VERSION == 8 && THRUST_SUBMINOR_VERSION < 3 // Workaround a bug in thrust 1.8.0 - 1.8.2 scan implementations which produces // wrong results #include #define THRUST_SCAN_WORKAROUND #endif namespace vtkm { namespace worklet { namespace internal { // TODO: The following code is meant to be temporary until similar functionality is // implemented elsewhere. // The current DeviceAdapterAlgorithm::Copy resizes the destination array, and // therefore, is not usable for our purpose here. template class CopyArray : public vtkm::exec::FunctorBase { public: typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::PortalConst SourcePortalType; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal DestPortalType; VTKM_CONT_EXPORT CopyArray(SourcePortalType input, DestPortalType output) : Input(input), Output(output) { } VTKM_EXEC_EXPORT void operator()(vtkm::Id idx) const { this->Output.Set(idx, this->Input.Get(idx)); } private: SourcePortalType Input; DestPortalType Output; }; template VTKM_CONT_EXPORT void ResizeArrayHandle(const vtkm::cont::ArrayHandle &input, vtkm::Id size, vtkm::cont::ArrayHandle &output, DeviceAdapter) { typedef vtkm::cont::DeviceAdapterAlgorithm Algorithm; output.Allocate(size); CopyArray copyArray(input.PrepareForInput(DeviceAdapter()), output.PrepareForInPlace(DeviceAdapter())); Algorithm::Schedule(copyArray, input.GetNumberOfValues()); } template VTKM_EXEC_EXPORT void swap(T &v1, T &v2) { T temp = v1; v1 = v2; v2 = temp; } template VTKM_EXEC_CONT_EXPORT T Scale(const T &val, vtkm::Float64 scale) { return static_cast(scale * static_cast(val)); } template VTKM_EXEC_CONT_EXPORT vtkm::Vec Scale(const vtkm::Vec &val, vtkm::Float64 scale) { return val * scale; } template class ExecutionConnectivityExplicit : vtkm::exec::ExecutionObjectBase { private: typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal UInt8Portal; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal IdComponentPortal; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal IdPortal; public: VTKM_CONT_EXPORT ExecutionConnectivityExplicit() : Shapes(), NumIndices(), Connectivity(), IndexOffsets() { } VTKM_CONT_EXPORT ExecutionConnectivityExplicit(const UInt8Portal &shapes, const IdComponentPortal &numIndices, const IdPortal &connectivity, const IdPortal &indexOffsets) : Shapes(shapes), NumIndices(numIndices), Connectivity(connectivity), IndexOffsets(indexOffsets) { } VTKM_EXEC_EXPORT void SetCellShape(vtkm::Id cellIndex, vtkm::UInt8 shape) { this->Shapes.Set(cellIndex, shape); } VTKM_EXEC_EXPORT void SetNumberOfIndices(vtkm::Id cellIndex, vtkm::IdComponent numIndices) { this->NumIndices.Set(cellIndex, numIndices); } VTKM_EXEC_EXPORT void SetIndexOffset(vtkm::Id cellIndex, vtkm::Id indexOffset) { this->IndexOffsets.Set(cellIndex, indexOffset); } VTKM_EXEC_EXPORT void SetConnectivity(vtkm::Id connectivityIndex, vtkm::Id pointIndex) { this->Connectivity.Set(connectivityIndex, pointIndex); } private: UInt8Portal Shapes; IdComponentPortal NumIndices; IdPortal Connectivity; IdPortal IndexOffsets; }; } // namespace internal struct ClipStats { vtkm::Id NumberOfCells; vtkm::Id NumberOfIndices; vtkm::Id NumberOfNewPoints; struct SumOp { VTKM_EXEC_CONT_EXPORT ClipStats operator()(const ClipStats &cs1, const ClipStats &cs2) const { ClipStats sum = cs1; sum.NumberOfCells += cs2.NumberOfCells; sum.NumberOfIndices += cs2.NumberOfIndices; sum.NumberOfNewPoints += cs2.NumberOfNewPoints; return sum; } }; }; struct EdgeInterpolation { vtkm::Id Vertex1, Vertex2; vtkm::Float64 Weight; struct LessThanOp { VTKM_EXEC_EXPORT bool operator()(const EdgeInterpolation &v1, const EdgeInterpolation &v2) const { return (v1.Vertex1 < v2.Vertex1) || (v1.Vertex1 == v2.Vertex1 && v1.Vertex2 < v2.Vertex2); } }; struct EqualToOp { VTKM_EXEC_EXPORT bool operator()(const EdgeInterpolation &v1, const EdgeInterpolation &v2) const { return v1.Vertex1 == v2.Vertex1 && v1.Vertex2 == v2.Vertex2; } }; }; VTKM_EXEC_CONT_EXPORT std::ostream& operator<<(std::ostream &out, const ClipStats &val) { return out << std::endl << "{ Cells: " << val.NumberOfCells << ", Indices: " << val.NumberOfIndices << ", NewPoints: " << val.NumberOfNewPoints << " }"; } VTKM_EXEC_CONT_EXPORT std::ostream& operator<<(std::ostream &out, const EdgeInterpolation &val) { return out << std::endl << "{ Vertex1: " << val.Vertex1 << ", Vertex2: " << val.Vertex2 << ", Weight: " << val.Weight << " }"; } template class Clip { private: typedef internal::ClipTables::DevicePortal ClipTablesPortal; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal IdPortal; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::PortalConst IdPortalConst; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal EdgeInterpolationPortal; typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::PortalConst EdgeInterpolationPortalConst; typedef vtkm::cont::DeviceAdapterAlgorithm Algorithm; public: struct TypeClipStats : vtkm::ListTagBase { }; class ComputeStats : public vtkm::worklet::WorkletMapPointToCell { public: typedef void ControlSignature(TopologyIn topology, FieldInPoint scalars, FieldOutCell clipTableIdxs, FieldOutCell stats); typedef void ExecutionSignature(_2, CellShape, FromCount, _3, _4); VTKM_CONT_EXPORT ComputeStats(vtkm::Float64 value, const ClipTablesPortal &clipTables) : Value(value), ClipTables(clipTables) { } template VTKM_EXEC_EXPORT void operator()(const ScalarsVecType &scalars, CellShapeTag shape, vtkm::Id count, vtkm::Id& clipTableIdx, ClipStats& stats) const { const vtkm::Id mask[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; vtkm::Id caseId = 0; for (vtkm::IdComponent i = 0; i < count; ++i) { caseId |= (static_cast(scalars[i]) > this->Value) ? mask[i] : 0; } vtkm::Id idx = this->ClipTables.GetCaseIndex(shape.Id, caseId); clipTableIdx = idx; vtkm::Id numberOfCells = this->ClipTables.ValueAt(idx++); vtkm::Id numberOfIndices = 0; vtkm::Id numberOfNewPoints = 0; for (vtkm::Id cell = 0; cell < numberOfCells; ++cell) { ++idx; // skip shape-id vtkm::Id npts = this->ClipTables.ValueAt(idx++); numberOfIndices += npts; while (npts--) { // value < 100 means a new point needs to be generated by clipping an edge numberOfNewPoints += (this->ClipTables.ValueAt(idx++) < 100) ? 1 : 0; } } stats.NumberOfCells = numberOfCells; stats.NumberOfIndices = numberOfIndices; stats.NumberOfNewPoints = numberOfNewPoints; } private: vtkm::Float64 Value; ClipTablesPortal ClipTables; }; class GenerateCellSet : public vtkm::worklet::WorkletMapPointToCell { public: typedef void ControlSignature(TopologyIn topology, FieldInPoint scalars, FieldInCell clipTableIdxs, FieldInCell cellSetIdxs, ExecObject connectivityExplicit, ExecObject interpolation, ExecObject newPointsConnectivityReverseMap); typedef void ExecutionSignature(CellShape, _2, FromIndices, _3, _4, _5, _6, _7); VTKM_CONT_EXPORT GenerateCellSet(vtkm::Float64 value, const ClipTablesPortal clipTables) : Value(value), ClipTables(clipTables) { } template VTKM_EXEC_EXPORT void operator()( CellShapeTag shape, const ScalarsVecType &scalars, const IndicesVecType &indices, vtkm::Id clipTableIdx, ClipStats cellSetIndices, internal::ExecutionConnectivityExplicit connectivityExplicit, vtkm::exec::ExecutionWholeArray interpolation, vtkm::exec::ExecutionWholeArray newPointsConnectivityReverseMap) const { vtkm::Id idx = clipTableIdx; // index of first cell vtkm::Id cellIdx = cellSetIndices.NumberOfCells; // index of first cell in connectivity array vtkm::Id connectivityIdx = cellSetIndices.NumberOfIndices; // index of new points generated by first cell vtkm::Id newPtsIdx = cellSetIndices.NumberOfNewPoints; vtkm::Id numberOfCells = this->ClipTables.ValueAt(idx++); for (vtkm::Id cell = 0; cell < numberOfCells; ++cell, ++cellIdx) { connectivityExplicit.SetCellShape(cellIdx, this->ClipTables.ValueAt(idx++)); vtkm::IdComponent numPoints = this->ClipTables.ValueAt(idx++); connectivityExplicit.SetNumberOfIndices(cellIdx, numPoints); connectivityExplicit.SetIndexOffset(cellIdx, connectivityIdx); for (vtkm::Id pt = 0; pt < numPoints; ++pt, ++idx) { vtkm::IdComponent entry = static_cast(this->ClipTables.ValueAt(idx)); if (entry >= 100) // existing point { connectivityExplicit.SetConnectivity(connectivityIdx++, indices[entry - 100]); } else // edge, new point to be generated by cutting the egde { internal::ClipTables::EdgeVec edge = this->ClipTables.GetEdge(shape.Id, entry); EdgeInterpolation ei; ei.Vertex1 = indices[edge[0]]; ei.Vertex2 = indices[edge[1]]; if (ei.Vertex1 > ei.Vertex2) { internal::swap(ei.Vertex1, ei.Vertex2); internal::swap(edge[0], edge[1]); } ei.Weight = (static_cast(scalars[edge[0]]) - this->Value) / static_cast(scalars[edge[0]] - scalars[edge[1]]); interpolation.Set(newPtsIdx , ei); newPointsConnectivityReverseMap.Set(newPtsIdx, connectivityIdx++); ++newPtsIdx; } } } } private: vtkm::Float64 Value; ClipTablesPortal ClipTables; }; // The following can be done using DeviceAdapterAlgorithm::LowerBounds followed by // a worklet for updating connectivity. We are going with a custom worklet, that // combines lower-bounds computation and connectivity update, because this is // currently faster and uses less memory. class AmendConnectivity : public vtkm::exec::FunctorBase { public: VTKM_CONT_EXPORT AmendConnectivity(EdgeInterpolationPortalConst newPoints, EdgeInterpolationPortalConst uniqueNewPoints, IdPortalConst newPointsConnectivityReverseMap, vtkm::Id newPointsOffset, IdPortal connectivity) : NewPoints(newPoints), UniqueNewPoints(uniqueNewPoints), NewPointsConnectivityReverseMap(newPointsConnectivityReverseMap), NewPointsOffset(newPointsOffset), Connectivity(connectivity) { } VTKM_EXEC_EXPORT void operator()(vtkm::Id idx) const { EdgeInterpolation current = this->NewPoints.Get(idx); typename EdgeInterpolation::LessThanOp lt; // find point index by looking up in the unique points array (binary search) vtkm::Id count = UniqueNewPoints.GetNumberOfValues(); vtkm::Id first = 0; while (count > 0) { vtkm::Id step = count/2; vtkm::Id mid = first + step; if (lt(this->UniqueNewPoints.Get(mid), current)) { first = ++mid; count -= step + 1; } else { count = step; } } this->Connectivity.Set(this->NewPointsConnectivityReverseMap.Get(idx), this->NewPointsOffset + first); } private: EdgeInterpolationPortalConst NewPoints; EdgeInterpolationPortalConst UniqueNewPoints; IdPortalConst NewPointsConnectivityReverseMap; vtkm::Id NewPointsOffset; IdPortal Connectivity; }; Clip() : ClipTablesInstance(), NewPointsInterpolation(), NewPointsOffset() { } template vtkm::cont::CellSetExplicit<> Run(const vtkm::cont::DynamicCellSet &cellSet, const ScalarsArrayHandle &scalars, vtkm::Float64 value) { DeviceAdapter device; ClipTablesPortal clipTablesDevicePortal = this->ClipTablesInstance.GetDevicePortal(device); // Step 1. compute counts for the elements of the cell set data structure vtkm::cont::ArrayHandle clipTableIdxs; vtkm::cont::ArrayHandle stats; ComputeStats computeStats(value, clipTablesDevicePortal); DispatcherMapTopology(computeStats).Invoke(cellSet, scalars, clipTableIdxs, stats); // compute offsets for each invocation ClipStats zero = { 0, 0, 0 }; vtkm::cont::ArrayHandle cellSetIndices; ClipStats total = Algorithm::ScanExclusive(stats, cellSetIndices, ClipStats::SumOp(), zero); stats.ReleaseResources(); // Step 2. generate the output cell set vtkm::cont::ArrayHandle shapes; vtkm::cont::ArrayHandle numIndices; vtkm::cont::ArrayHandle connectivity; vtkm::cont::ArrayHandle cellToConnectivityMap; internal::ExecutionConnectivityExplicit outConnectivity( shapes.PrepareForOutput(total.NumberOfCells, device), numIndices.PrepareForOutput(total.NumberOfCells, device), connectivity.PrepareForOutput(total.NumberOfIndices, device), cellToConnectivityMap.PrepareForOutput(total.NumberOfCells, device)); vtkm::cont::ArrayHandle newPoints; // reverse map from the new points to connectivity array vtkm::cont::ArrayHandle newPointsConnectivityReverseMap; GenerateCellSet generateCellSet(value, clipTablesDevicePortal); DispatcherMapTopology(generateCellSet).Invoke(cellSet, scalars, clipTableIdxs, cellSetIndices, outConnectivity, vtkm::exec::ExecutionWholeArray< EdgeInterpolation, VTKM_DEFAULT_STORAGE_TAG, DeviceAdapter>(newPoints, total.NumberOfNewPoints), vtkm::exec::ExecutionWholeArray< vtkm::Id, VTKM_DEFAULT_STORAGE_TAG, DeviceAdapter>(newPointsConnectivityReverseMap, total.NumberOfNewPoints)); cellSetIndices.ReleaseResources(); // Step 3. remove duplicates from the list of new points vtkm::cont::ArrayHandle uniqueNewPoints; Algorithm::SortByKey(newPoints, newPointsConnectivityReverseMap, EdgeInterpolation::LessThanOp()); Algorithm::Copy(newPoints, uniqueNewPoints); Algorithm::Unique(uniqueNewPoints, EdgeInterpolation::EqualToOp()); this->NewPointsInterpolation = uniqueNewPoints; this->NewPointsOffset = scalars.GetNumberOfValues(); // Step 4. update the connectivity array with indexes to the new, unique points AmendConnectivity computeNewPointsConnectivity( newPoints.PrepareForInput(device), uniqueNewPoints.PrepareForInput(device), newPointsConnectivityReverseMap.PrepareForInput(device), this->NewPointsOffset, connectivity.PrepareForInPlace(device)); Algorithm::Schedule(computeNewPointsConnectivity, total.NumberOfNewPoints); vtkm::cont::CellSetExplicit<> output; output.Fill(shapes, numIndices, connectivity); return output; } template class ClipWithImplicitFunction { public: VTKM_CONT_EXPORT ClipWithImplicitFunction(Clip *clipper, const vtkm::cont::DynamicCellSet &cellSet, ImplicitFunction function, vtkm::cont::CellSetExplicit<> *result) : Clipper(clipper), CellSet(&cellSet), Function(function), Result(result) { } template VTKM_CONT_EXPORT void operator()(const ArrayHandleType &handle) const { vtkm::cont::ArrayHandleTransform< vtkm::FloatDefault, ArrayHandleType, vtkm::ImplicitFunctionValue > clipScalars(handle, this->Function); *this->Result = this->Clipper->Run(*this->CellSet, clipScalars, 0.0); } private: Clip *Clipper; const vtkm::cont::DynamicCellSet *CellSet; const vtkm::ImplicitFunctionValue Function; vtkm::cont::CellSetExplicit<> *Result; }; template vtkm::cont::CellSetExplicit<> Run(const vtkm::cont::DynamicCellSet &cellSet, const ImplicitFunction &clipFunction, const vtkm::cont::CoordinateSystem &coords) { vtkm::cont::CellSetExplicit<> output; ClipWithImplicitFunction clip(this, cellSet, clipFunction, &output); coords.GetData().CastAndCall(clip); return output; } class InterpolateField { public: template class Kernel : public vtkm::exec::FunctorBase { public: typedef typename vtkm::cont::ArrayHandle ::template ExecutionTypes::Portal FieldPortal; VTKM_CONT_EXPORT Kernel(EdgeInterpolationPortalConst interpolation, vtkm::Id newPointsOffset, FieldPortal field) : Interpolation(interpolation), NewPointsOffset(newPointsOffset), Field(field) { } VTKM_EXEC_EXPORT void operator()(vtkm::Id idx) const { EdgeInterpolation ei = this->Interpolation.Get(idx); T v1 = Field.Get(ei.Vertex1); T v2 = Field.Get(ei.Vertex2); Field.Set(this->NewPointsOffset + idx, internal::Scale(T(v2 - v1), ei.Weight) + v1); } private: EdgeInterpolationPortalConst Interpolation; vtkm::Id NewPointsOffset; FieldPortal Field; }; template VTKM_CONT_EXPORT void operator()(const vtkm::cont::ArrayHandle &field) const { vtkm::Id count = this->InterpolationArray.GetNumberOfValues(); vtkm::cont::ArrayHandle result; internal::ResizeArrayHandle(field, field.GetNumberOfValues() + count, result, DeviceAdapter()); Kernel kernel( this->InterpolationArray.PrepareForInput(DeviceAdapter()), this->NewPointsOffset, result.PrepareForInPlace(DeviceAdapter())); Algorithm::Schedule(kernel, count); *(this->Output) = vtkm::cont::DynamicArrayHandle(result); } VTKM_CONT_EXPORT InterpolateField( vtkm::cont::ArrayHandle interpolationArray, vtkm::Id newPointsOffset, vtkm::cont::DynamicArrayHandle *output) : InterpolationArray(interpolationArray), NewPointsOffset(newPointsOffset), Output(output) { } private: vtkm::cont::ArrayHandle InterpolationArray; vtkm::Id NewPointsOffset; vtkm::cont::DynamicArrayHandle *Output; }; template vtkm::cont::DynamicArrayHandle ProcessField( const DynamicArrayHandleType &fieldData) const { vtkm::cont::DynamicArrayHandle output; fieldData.CastAndCall(InterpolateField(this->NewPointsInterpolation, this->NewPointsOffset, &output)); return output; } private: internal::ClipTables ClipTablesInstance; vtkm::cont::ArrayHandle NewPointsInterpolation; vtkm::Id NewPointsOffset; }; } } // namespace vtkm::worklet #if defined(THRUST_SCAN_WORKAROUND) namespace thrust { namespace detail { // causes a different code path which does not have the bug template<> struct is_integral : public true_type {}; } } // namespace thrust::detail #endif #endif // vtkm_m_worklet_Clip_h