//============================================================================
//  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 (c) 2018, The Regents of the University of California, through
// Lawrence Berkeley National Laboratory (subject to receipt of any required approvals
// from the U.S. Dept. of Energy).  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// (1) Redistributions of source code must retain the above copyright notice, this
//     list of conditions and the following disclaimer.
//
// (2) Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
// (3) Neither the name of the University of California, Lawrence Berkeley National
//     Laboratory, U.S. Dept. of Energy nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//
//=============================================================================
//
//  This code is an extension of the algorithm presented in the paper:
//  Parallel Peak Pruning for Scalable SMP Contour Tree Computation.
//  Hamish Carr, Gunther Weber, Christopher Sewell, and James Ahrens.
//  Proceedings of the IEEE Symposium on Large Data Analysis and Visualization
//  (LDAV), October 2016, Baltimore, Maryland.
//
//  The PPP2 algorithm and software were jointly developed by
//  Hamish Carr (University of Leeds), Gunther H. Weber (LBNL), and
//  Oliver Ruebel (LBNL)
//==============================================================================

#ifndef vtk_m_worklet_contourtree_distributed_boundary_tree_maker_h
#define vtk_m_worklet_contourtree_distributed_boundary_tree_maker_h

// augmented contour tree includes
#include <vtkm/worklet/contourtree_augmented/ContourTree.h>
#include <vtkm/worklet/contourtree_augmented/PrintVectors.h>
#include <vtkm/worklet/contourtree_augmented/Types.h>
#include <vtkm/worklet/contourtree_augmented/data_set_mesh/IdRelabeler.h>

// distibuted contour tree includes
#include <vtkm/worklet/contourtree_distributed/BoundaryTree.h>
#include <vtkm/worklet/contourtree_distributed/HierarchicalContourTree.h>
#include <vtkm/worklet/contourtree_distributed/InteriorForest.h>

#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/AddTerminalFlagsToUpDownNeighboursWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/AugmentBoundaryWithNecessaryInteriorSupernodesAppendNecessarySupernodesWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/AugmentBoundaryWithNecessaryInteriorSupernodesUnsetBoundarySupernodesWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/BoundaryTreeNodeComparator.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/BoundaryVerticesPerSuperArcWorklets.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/CompressRegularisedNodesCopyNecessaryRegularNodesWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/CompressRegularisedNodesFillBoundaryTreeSuperarcsWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/CompressRegularisedNodesFindNewSuperarcsWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/CompressRegularisedNodesResolveRootWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/CompressRegularisedNodesTransferVerticesWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/ContourTreeNodeHyperArcComparator.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/FindBoundaryTreeSuperarcsSuperarcToWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/FindBoundaryVerticesIsNecessaryWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/FindNecessaryInteriorSetSuperparentNecessaryWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/FindNecessaryInteriorSupernodesFindNodesWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/HyperarcComparator.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/IdentifyRegularisedSupernodesStepOneWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/IdentifyRegularisedSupernodesStepTwoWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/NoSuchElementFunctor.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/PointerDoubleUpDownNeighboursWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/PropagateBoundaryCountsComputeGroupTotalsWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/PropagateBoundaryCountsSubtractDependentCountsWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/PropagateBoundaryCountsTransferCumulativeCountsWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/PropagateBoundaryCountsTransferDependentCountsWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/SetInteriorForestWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/SetUpAndDownNeighboursWorklet.h>
#include <vtkm/worklet/contourtree_distributed/boundary_tree_maker/SumFunctor.h>


// vtkm includes
#include <vtkm/Types.h>

// std includes
#include <sstream>
#include <string>
#include <utility>

// TODO: Change all pointers to std mangaged pointers or use references (the memory for those pointers should be managed outside in the filter)

namespace vtkm
{
namespace worklet
{
namespace contourtree_distributed
{

/// \brief Class to compute the Boundary Restricted Augmented Contour Tree (BRACT), a.k.a., BoundaryTree
///
template <typename MeshType, typename MeshBoundaryExecObjType>
class BoundaryTreeMaker
{
public:
  // pointers to underlying data structures
  /// Pointer to the input mesh
  MeshType* Mesh;
  MeshBoundaryExecObjType& MeshBoundaryExecutionObject;

  /// Pointer to the contour tree for the mesh
  vtkm::worklet::contourtree_augmented::ContourTree& ContourTree;
  /// Data structure for storing the results from this class
  BoundaryTree* BoundaryTreeData;
  /// Data structure for the interior forest of a data block, i.e, the contourtree of a block minus the BoundaryTree (also called the residue or BRACT)
  InteriorForest* InteriorForestData;

  /// how many vertices ARE on the boundary
  vtkm::Id NumBoundary;

  /// how many interior vertices are necessary
  vtkm::Id NumNecessary;

  /// how many vertices are kept in the BRACT
  vtkm::Id NumKept;

  /// arrays for computation - stored here to simplify debug print

  //  arrays sized to all regular vertices - this may not be necessary, but is robust
  /// array for ID in boundary tree
  vtkm::worklet::contourtree_augmented::IdArrayType BoundaryTreeId;

  // arrays sized to number of boundary vertices
  /// the regular IDs of the boundary vertices (a conservative over-estimate, needed for hierarchical computation)
  vtkm::worklet::contourtree_augmented::IdArrayType BoundaryVertexSuperset;
  /// their sort indices (may be redundant, but . . .)
  vtkm::worklet::contourtree_augmented::IdArrayType BoundaryIndices;
  /// the superparents for each boundary vertex
  vtkm::worklet::contourtree_augmented::IdArrayType BoundarySuperparents;

  // arrays sized to number of supernodes/superarcs
  /// these are essentially the same as the transfer/intrinsic/dependent weights
  /// probably about time to refactor and do a generic hyperarc propagation routine (owch!)

  /// mapping from tree super ID to bract superset ID (could potentially be combined with isNecessary)
  vtkm::worklet::contourtree_augmented::IdArrayType TreeToSuperset;

  /// count of boundary nodes on each superarc
  // TODO: May be able to reuse Petar's Hypersweep, which uses less storage AND/OR use Petar's generic hypersweep to simplify code
  vtkm::worklet::contourtree_augmented::IdArrayType SuperarcIntrinsicBoundaryCount;
  /// count of boundary nodes being transferred at eah supernode
  vtkm::worklet::contourtree_augmented::IdArrayType SupernodeTransferBoundaryCount;
  /// count of dependent boundary nodes for each superarc
  vtkm::worklet::contourtree_augmented::IdArrayType SuperarcDependentBoundaryCount;
  /// count of dependent boundary nodes for each hyperarc
  vtkm::worklet::contourtree_augmented::IdArrayType HyperarcDependentBoundaryCount;

  // vectors needed for collapsing out "regular" supernodes
  // up- and down- neighbours in the tree (unique for regular nodes)
  // sized to BRACT
  vtkm::worklet::contourtree_augmented::IdArrayType UpNeighbour;
  vtkm::worklet::contourtree_augmented::IdArrayType DownNeighbour;
  /// array needed for compression
  vtkm::worklet::contourtree_augmented::IdArrayType NewVertexId;

  VTKM_CONT
  BoundaryTreeMaker(MeshType* inputMesh,
                    MeshBoundaryExecObjType& meshBoundaryExecObj,
                    vtkm::worklet::contourtree_augmented::ContourTree& inputTree,
                    BoundaryTree* boundaryTree,
                    InteriorForest* interiorTree)
    : Mesh(inputMesh)
    , MeshBoundaryExecutionObject(meshBoundaryExecObj)
    , ContourTree(inputTree)
    , BoundaryTreeData(boundaryTree)
    , InteriorForestData(interiorTree)
    , NumBoundary(0)
    , NumNecessary(0)
  {
  }

  /// computes a BRACT from a contour tree for a known block
  /// note the block ID for debug purposes
  /// @param[in] localToGlobalIdRelabeler IdRelabeler for the mesh needed to call
  ///            this->Mesh->GetGlobalIdsFromMeshIndices(...)  used by the
  ///            this->SetInteriorForest function (default=nullptr).
  /// @param[in] boundaryCritical If True then use only boundary critical points
  ///            in the BoundaryTree, otherwise use the full boundary between blocks.
  ///            (default=true)
  VTKM_CONT
  void Construct(const vtkm::worklet::contourtree_augmented::mesh_dem::IdRelabeler*
                   localToGlobalIdRelabeler = nullptr,
                 bool boundaryCritical = true);

  /// routine to find the set of boundary vertices
  /// @param[in] boundaryCritical If True then use only boundary critical points
  ///            in the BoundaryTree, otherwise use the full boundary between blocks.
  ///            (default=true)
  VTKM_CONT
  void FindBoundaryVertices(bool boundaryCritical);

  /// routine to compute the initial dependent counts (i.e. along each superarc)
  /// in preparation for hyper-propagation
  VTKM_CONT
  void ComputeDependentBoundaryCounts();

  /// routine for hyper-propagation to compute dependent boundary counts
  VTKM_CONT
  void PropagateBoundaryCounts();

  /// routine to find the necessary interior supernodes for the BRACT
  VTKM_CONT
  void FindNecessaryInteriorSupernodes();

  /// routine to add the necessary interior supernodes to the boundary array
  VTKM_CONT
  void AugmentBoundaryWithNecessaryInteriorSupernodes();

  /// routine that sorts on hyperparent to find BRACT superarcs
  VTKM_CONT
  void FindBoundaryTreeSuperarcs();

  /// compresses out supernodes in the interior that have become regular in the BRACT
  VTKM_CONT
  void SuppressRegularisedInteriorSupernodes();

  /// routine to find *AN* up/down neighbour for each vertex
  /// this is deliberately non-canonical and exploits write-conflicts
  VTKM_CONT
  void SetUpAndDownNeighbours();

  /// routine to set a flag for each vertex that has become regular in the interior of the BRACT
  VTKM_CONT
  void IdentifyRegularisedSupernodes();

  /// this routine sets a flag on every up/down neighbour that points to a critical point
  /// to force termination of pointer-doubling
  VTKM_CONT
  void AddTerminalFlagsToUpDownNeighbours();

  /// routine that uses pointer-doubling to collapse regular nodes in the BRACT
  VTKM_CONT
  void PointerDoubleUpDownNeighbours();

  /// routine that compresses the regular nodes out of the BRACT
  VTKM_CONT
  void CompressRegularisedNodes();

  /// sets the arrays in the InteriorForest (i.e., the residue) that need to be passed to the grafting stage.
  /// @param[in] localToGlobalIdRelabeler IdRelabeler for the mesh needed to call
  ///            this->Mesh->GetGlobalIdsFromMeshIndices(...)  (default=nullptr).
  VTKM_CONT
  void SetInteriorForest(const vtkm::worklet::contourtree_augmented::mesh_dem::IdRelabeler*
                           localToGlobalIdRelabeler = nullptr);

  /// prints the contents of the restrictor object in a standard format
  VTKM_CONT
  std::string DebugPrint(const char* message, const char* fileName, long lineNum) const;

private:
  /// Used internally to Invoke worklets
  vtkm::cont::Invoker Invoke;

}; // BoundaryTreeMaker class


template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::Construct(
  const vtkm::worklet::contourtree_augmented::mesh_dem::IdRelabeler* localToGlobalIdRelabeler,
  bool boundaryCritical)
{ // Construct

  // 0.  Retrieve the number of iterations used to construct the contour tree
  //    NB: There may be sense in reusing transfer & dependent weight arrays,
  //    and nIterations, and the arrays for the super/hyper arc subsegments per pass
  //    but for now, burn extra memory
  //    The +1 is because this is 0-indexed

  // Step I: Initialise the bract to hold the set of boundary vertices
  //        & save how many for later
  this->FindBoundaryVertices(boundaryCritical);

  // Step II: For each supernode / superarc, compute the dependent boundary counts
  this->ComputeDependentBoundaryCounts();

  // Step III: We now have initial weights and do the standard inward propagation through
  // the hyperstructure to compute properly over the entire tree
  this->PropagateBoundaryCounts();

  // Step IV:  We now use the dependent weight to identify which internal supernodes are necessary
  this->FindNecessaryInteriorSupernodes();

  // Step V: Add the necessary interior nodes to the end of the boundary set
  this->AugmentBoundaryWithNecessaryInteriorSupernodes();

  // Step VI: Use the hyperparents to sort these vertices into contiguous chunks as usual
  //          We will store the BRACT ID for the superarc target this to simplify the next step
  this->FindBoundaryTreeSuperarcs();

  // Step VII: Suppress interior supernodes that are regular in the BRACT
  //           At the end, we will reset the superarc target from BRACT ID to block ID
  this->SuppressRegularisedInteriorSupernodes();

  // Step VIII: Set the Residue for passing to the TreeGrafter
  this->SetInteriorForest(localToGlobalIdRelabeler);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->BoundaryTreeData->DebugPrint("All Completed\n", __FILE__, __LINE__));
#endif
} // Construct


/// routine to find the set of boundary vertices
///
/// Side-effects: This function updates:
///   - this->BoundaryVertexSuperset
///   - this->BoundaryIndices
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::FindBoundaryVertices(
  bool boundaryCritical)
{ // FindBoundaryVertices

  // ask the mesh to give us a list of boundary verticels (with their regular indices)
  this->Mesh->GetBoundaryVertices(
    this->BoundaryVertexSuperset, this->BoundaryIndices, &(this->MeshBoundaryExecutionObject));
  // pull a local copy of the size (they can diverge)
  this->BoundaryTreeData->NumBoundary = this->BoundaryVertexSuperset.GetNumberOfValues();
  // Identify the points that are boundary critical and update this->BoundaryVertexSuperset,
  // and this->BoundaryIndices accordingly by removing all boundary vertices that are
  // not boundary cirtical, and hence, are not neccessary for merging neighboring data blocks
  if (boundaryCritical)
  {
    vtkm::worklet::contourtree_distributed::bract_maker::FindBoundaryVerticesIsNecessaryWorklet
      isNecessaryWorklet;
    vtkm::cont::ArrayHandle<bool> isBoundaryCritical;
    this->Invoke(isNecessaryWorklet,
                 this->BoundaryVertexSuperset,
                 &(this->MeshBoundaryExecutionObject),
                 isBoundaryCritical);
    vtkm::worklet::contourtree_augmented::IdArrayType necessaryBoundaryVertexSuperset;
    vtkm::worklet::contourtree_augmented::IdArrayType necessaryBoundaryIndices;
    vtkm::cont::Algorithm::CopyIf(
      this->BoundaryVertexSuperset, isBoundaryCritical, necessaryBoundaryVertexSuperset);
    this->BoundaryVertexSuperset = necessaryBoundaryVertexSuperset;
    vtkm::cont::Algorithm::CopyIf(
      this->BoundaryIndices, isBoundaryCritical, necessaryBoundaryIndices);
    this->BoundaryIndices = necessaryBoundaryIndices;
  }

  this->NumBoundary = this->BoundaryVertexSuperset.GetNumberOfValues();
  this->BoundaryTreeData->NumBoundaryUsed = this->NumBoundary;

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Boundary Vertices Set", __FILE__, __LINE__));
#endif
} // FindBoundaryVertices


/// routine to compute the initial dependent counts (i.e. along each superarc)
/// in preparation for hyper-propagation
///
/// Side-effects: This function updates:
///   - this->BoundarySuperparents
///   - this->SuperarcIntrinsicBoundaryCount
///   - this->BoundarySuperparents
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::ComputeDependentBoundaryCounts()
{ // ComputeDependentBoundaryCounts
  // 1.  copy in the superparent from the regular arrays in the contour tree
  auto permutedContourTreeSuperparents =
    vtkm::cont::make_ArrayHandlePermutation(this->BoundaryIndices, this->ContourTree.Superparents);
  vtkm::cont::Algorithm::Copy(permutedContourTreeSuperparents, this->BoundarySuperparents);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info, this->DebugPrint("Superparents Set", __FILE__, __LINE__));
#endif

  // 2. Sort this set & count by superarc to set initial intrinsic boundary counts
  //    Note that this is in the parallel style, but can be done more efficiently in serial
  //    a. Allocate space for the count & initialise to zero
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(0, this->ContourTree.Superarcs.GetNumberOfValues()),
    this->SuperarcIntrinsicBoundaryCount);
  //   b. sort the superparents
  vtkm::cont::Algorithm::Sort(this->BoundarySuperparents);

  // c.  Now compute the number of boundary vertices per superarc
  //    This *could* be done with a prefix sum, but it's cheaper to do it this way with 2 passes
  //    NB: first superarc's beginning is always 0, so we can omit it, which simplifies the IF logic
  //    i. Start by detecting the high end of the range and then ii) setting the last element explicitly
  bract_maker::BoundaryVerticiesPerSuperArcStepOneWorklet tempWorklet1(this->NumBoundary);
  this->Invoke(tempWorklet1,
               this->BoundarySuperparents,
               this->SuperarcIntrinsicBoundaryCount // output
  );

  // iii.Repeat to subtract and compute the extent lengths (i.e. the counts)
  //     Note that the 0th element will subtract 0 and can be omitted
  bract_maker::BoundaryVerticiesPerSuperArcStepTwoWorklet tempWorklet2;
  this->Invoke(tempWorklet2,
               this->BoundarySuperparents,
               this->SuperarcIntrinsicBoundaryCount // output
  );

  // resize local array
  this->BoundarySuperparents.ReleaseResources();

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Initial Counts Set", __FILE__, __LINE__));
#endif
} // ComputeDependentBoundaryCounts


/// routine for hyper-propagation to compute dependent boundary counts
///
///  Side-effects: This function updates:
///    - this->SupernodeTransferBoundaryCount
///    - this->SuperarcDependentBoundaryCount
///    - this->HyperarcDependentBoundaryCount
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::PropagateBoundaryCounts()
{ // PropagateBoundaryCounts
  //   1.  Propagate boundary counts inwards along super/hyperarcs (same as ComputeWeights)
  //    a.  Initialise arrays for transfer & dependent counts
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(0, this->ContourTree.Supernodes.GetNumberOfValues()),
    this->SupernodeTransferBoundaryCount);
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(0, this->ContourTree.Superarcs.GetNumberOfValues()),
    this->SuperarcDependentBoundaryCount);
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(0, this->ContourTree.Hyperarcs.GetNumberOfValues()),
    this->HyperarcDependentBoundaryCount);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Arrays initialised", __FILE__, __LINE__));
#endif

  // b.  Iterate, propagating counts inwards
  for (vtkm::Id iteration = 0; iteration < this->ContourTree.NumIterations; iteration++)
  { // b. per iteration
#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info, this->DebugPrint("Top of Loop:", __FILE__, __LINE__));
#endif
    // i. Pull the array bounds into register
    vtkm::Id firstSupernode =
      vtkm::cont::ArrayGetValue(iteration, this->ContourTree.FirstSupernodePerIteration);
    vtkm::Id lastSupernode =
      vtkm::cont::ArrayGetValue(iteration + 1, this->ContourTree.FirstSupernodePerIteration);

    if (lastSupernode == firstSupernode)
    {
#ifdef DEBUG_PRINT
      VTKM_LOG_S(vtkm::cont::LogLevel::Info,
                 "BoundaryTreeMaker::PropagateBoundaryCounts(): lastSupernode == firstSupernode "
                 " -> Skipping iteration");
#endif
      continue;
    }

    vtkm::Id firstHypernode =
      vtkm::cont::ArrayGetValue(iteration, this->ContourTree.FirstHypernodePerIteration);
    vtkm::Id lastHypernode =
      vtkm::cont::ArrayGetValue(iteration + 1, this->ContourTree.FirstHypernodePerIteration);

    //  ii.  Add xfer + int & store in dependent count
    //      Compute the sum of this->SupernodeTransferBoundaryCount and this->SuperarcIntrinsicBoundaryCount
    //      for the [firstSupernodex, lastSupernode) subrange and copy to the this->SuperarcDependentBoundaryCount
    { // make local context so fancyTempSumArray gets deleted
      auto fancyTempZippedArray = vtkm::cont::make_ArrayHandleZip(
        this->SupernodeTransferBoundaryCount, this->SuperarcIntrinsicBoundaryCount);
      auto fancyTempSumArray =
        vtkm::cont::make_ArrayHandleTransform(fancyTempZippedArray, bract_maker::SumFunctor{});

      vtkm::cont::Algorithm::CopySubRange(
        fancyTempSumArray,                    // input array
        firstSupernode,                       // start index for the copy
        lastSupernode - firstSupernode,       // number of values to copy
        this->SuperarcDependentBoundaryCount, // target output array
        firstSupernode // index in the output array where we start writing values to
      );
    } // end local context for step ii

#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info, this->DebugPrint("After Transfer", __FILE__, __LINE__));
#endif
    // iii.Perform prefix sum on dependent count range
    { // make local context so tempArray and fancyRangeArraySuperarcDependentBoundaryCountget  deleted
      auto fancyRangeArraySuperarcDependentBoundaryCount = make_ArrayHandleView(
        this->SuperarcDependentBoundaryCount, firstSupernode, lastSupernode - firstSupernode);
      // Write to temporary array first as it is not clear whether ScanInclusive is safe to read and write
      // to the same array and range
      vtkm::worklet::contourtree_augmented::IdArrayType tempArray;
      vtkm::cont::Algorithm::ScanInclusive(fancyRangeArraySuperarcDependentBoundaryCount,
                                           tempArray);
      vtkm::cont::Algorithm::CopySubRange(
        tempArray,
        0,
        tempArray.GetNumberOfValues(), // copy all of tempArray
        this->SuperarcDependentBoundaryCount,
        firstSupernode // to the SuperarcDependentBoundaryCound starting at firstSupernode
      );
    } // end local context for step iii

#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info,
               this->DebugPrint("After Prefix Sum", __FILE__, __LINE__));
#endif
    //  iv.  Subtract out the dependent count of the prefix to the entire hyperarc
    { // make local context for iv so newSuperArcDependentBoundaryCount and the worklet gets deleted
      // Storage for the vector portion that will be modified
      vtkm::worklet::contourtree_augmented::IdArrayType newSuperArcDependentBoundaryCount;
      vtkm::cont::Algorithm::CopySubRange(this->SuperarcDependentBoundaryCount,
                                          firstSupernode,
                                          lastSupernode - firstSupernode,
                                          newSuperArcDependentBoundaryCount);
      auto subtractDependentCountsWorklet =
        bract_maker::PropagateBoundaryCountsSubtractDependentCountsWorklet(firstSupernode,
                                                                           firstHypernode);
      // per supenode
      this->Invoke(
        subtractDependentCountsWorklet,
        // use backwards counting array for consistency with original code, but if forward or backward doesn't matter
        vtkm::cont::ArrayHandleCounting<vtkm::Id>(
          lastSupernode - 1, -1, lastSupernode - firstSupernode - 1), // input
        this->ContourTree.Hyperparents,                               // input
        this->ContourTree.Hypernodes,                                 // input
        this->SuperarcDependentBoundaryCount,                         // input
        newSuperArcDependentBoundaryCount                             // (input/output)
      );
      // copy the results back into our main array
      vtkm::cont::Algorithm::CopySubRange(newSuperArcDependentBoundaryCount,
                                          0,
                                          newSuperArcDependentBoundaryCount.GetNumberOfValues(),
                                          this->SuperarcDependentBoundaryCount,
                                          firstSupernode);
    } // end local context of iv
#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info,
               this->DebugPrint("After Hyperarc Subtraction", __FILE__, __LINE__));
#endif

    //  v.  Transfer the dependent count to the hyperarc's target supernode
    { // local context of v.
      auto transferDependentCountsWorklet =
        bract_maker::PropagateBoundaryCountsTransferDependentCountsWorklet(
          this->ContourTree.Supernodes.GetNumberOfValues(),
          this->ContourTree.Hypernodes.GetNumberOfValues());
      this->Invoke(
        transferDependentCountsWorklet,
        // for (vtkm::Id hypernode = firstHypernode; hypernode < lastHypernode; hypernode++)
        vtkm::cont::ArrayHandleCounting<vtkm::Id>(
          firstHypernode, 1, lastHypernode - firstHypernode), // input
        this->ContourTree.Hypernodes,                         // input
        this->SuperarcDependentBoundaryCount,                 // input
        this->HyperarcDependentBoundaryCount                  // output
      );
      // transferring the count is done as a separate reduction
    } // end local context for step iv
#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info,
               this->DebugPrint("After Dependent Count transfer", __FILE__, __LINE__));
#endif

    // next we want to end up summing transfer count & storing in the target.
    // Unfortunately, there may be multiple hyperarcs in a given
    // pass that target the same supernode, so we have to do this separately.
    // 1. permute so that all hypernodes with the same target are contiguous
    { // local context for summing transfer count & storing in the target.
      vtkm::worklet::contourtree_augmented::IdArrayType hyperarcTargetSortPermutation;
      vtkm::cont::Algorithm::Copy(vtkm::cont::ArrayHandleCounting<vtkm::Id>(
                                    firstHypernode, 1, lastHypernode - firstHypernode),
                                  hyperarcTargetSortPermutation);

      // 2. sort the elements to cluster by hyperarc target, using a lambda function for comparator
      //    we need a reference that a lambda function can use
      auto hyperarcComparator = bract_maker::HyperarcComparator(this->ContourTree.Hyperarcs);
      vtkm::cont::Algorithm::Sort(hyperarcTargetSortPermutation, hyperarcComparator);

      // 3. now compute the partial sum for the properly permuted boundary counts
      // total boundary count at each node
      vtkm::worklet::contourtree_augmented::IdArrayType accumulatedBoundaryCount;
      auto permutedHyperarcDependentCount =
        vtkm::cont::make_ArrayHandlePermutation(hyperarcTargetSortPermutation,       // id array
                                                this->HyperarcDependentBoundaryCount // value array
        );
      vtkm::cont::Algorithm::ScanInclusive(permutedHyperarcDependentCount,
                                           accumulatedBoundaryCount);

      // 4. The partial sum is now over ALL hypertargets, so within each group we need to subtract the first from the last
      // To do so, the last hyperarc in each cluster copies its cumulative count to the output array
      auto transferCumulativeCountsWorklet =
        bract_maker::PropagateBoundaryCountsTransferCumulativeCountsWorklet();
      this->Invoke(transferCumulativeCountsWorklet,
                   hyperarcTargetSortPermutation,
                   this->ContourTree.Hyperarcs,
                   accumulatedBoundaryCount,
                   this->SupernodeTransferBoundaryCount);

#ifdef DEBUG_PRINT
      VTKM_LOG_S(vtkm::cont::LogLevel::Info,
                 this->DebugPrint("After Tail Addition", __FILE__, __LINE__));
#endif

      // 5. Finally, we subtract the beginning of the group to get the total for each group
      // Note that we start the loop from 1, which means we don't need a special case if statement
      // because the prefix sum of the first element is the correct value anyway
      auto computeGroupTotalsWorklet =
        bract_maker::PropagateBoundaryCountsComputeGroupTotalsWorklet();
      this->Invoke(computeGroupTotalsWorklet,
                   hyperarcTargetSortPermutation,
                   this->ContourTree.Hyperarcs,
                   accumulatedBoundaryCount,
                   this->SupernodeTransferBoundaryCount);
    } // end  local context for summing transfer count & storing in the target.
#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info,
               this->DebugPrint("After Hyperarc Transfer", __FILE__, __LINE__));
#endif
  } // b. per iteration

  // when we are done, we need to force the summation for the root node, JUST IN CASE it is a boundary node itself
  // BTW, the value *SHOULD* be the number of boundary nodes, anyway
  vtkm::Id rootSuperId = this->ContourTree.Supernodes.GetNumberOfValues() - 1;
  vtkm::worklet::contourtree_augmented::IdArraySetValue(
    rootSuperId,
    vtkm::cont::ArrayGetValue(rootSuperId, this->SupernodeTransferBoundaryCount) +
      vtkm::cont::ArrayGetValue(rootSuperId, this->SuperarcIntrinsicBoundaryCount),
    this->SuperarcDependentBoundaryCount);
  vtkm::worklet::contourtree_augmented::IdArraySetValue(
    this->ContourTree.Hypernodes.GetNumberOfValues() - 1,
    vtkm::cont::ArrayGetValue(rootSuperId, this->SuperarcDependentBoundaryCount),
    this->HyperarcDependentBoundaryCount);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Iterations Complete", __FILE__, __LINE__));
#endif
} // PropagateBoundaryCounts


/// routine to find the necessary interior supernodes for the BRACT
///
/// INVARIANT:
/// We have now computed the dependent weight for each supernode
///    For boundary nodes, we will ignore this
///    For non-boundary nodes, if the dependent weight is 0 or nBoundary
///    then all boundary nodes are in one direction, and the node is unnecessary
/// We have decided that if a superarc has any boundary nodes, the entire superarc
/// should be treated as necessary. This extends the criteria so that the superparent and superparent's
/// superarc of any boundary node are necessary
///
/// Side-effects: This function updates:
///    - this->InteriorForestData->IsNecessary
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::FindNecessaryInteriorSupernodes()
{ // FindNecessaryInteriorSupernodes
  //  1. Identify the necessary supernodes (between two boundary points & still critical)
  //  1.A.  Start by setting all of them to "unnecessary"
  // Initalize isNecessary with False
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(false, this->ContourTree.Supernodes.GetNumberOfValues()),
    this->InteriorForestData->IsNecessary);
  // 1.B.  Our condition is that if the superarc dependent count is neither 0 nor the # of boundary
  //       points, the superarc target is necessary. Note that there may be write conflicts, but it's
  //       an OR operation, so it doesn't matter
  auto findNodesWorklet =
    bract_maker::FindNecessaryInteriorSupernodesFindNodesWorklet(this->NumBoundary);
  this->Invoke(findNodesWorklet,
               this->ContourTree.Superarcs,          // input
               this->SuperarcDependentBoundaryCount, // input
               this->InteriorForestData->IsNecessary // output
  );
#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Is Necessary Based on Dependency", __FILE__, __LINE__));
#endif

  // separate pass to set the superparent of every boundary node to be necessary
  auto setSuperparentNecessaryWorklet =
    bract_maker::FindNecessaryInteriorSetSuperparentNecessaryWorklet();
  this->Invoke(setSuperparentNecessaryWorklet,
               this->BoundaryIndices,
               this->ContourTree.Superparents,
               this->ContourTree.Superarcs,
               this->InteriorForestData->IsNecessary);
#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info, this->DebugPrint("Is Necessary Set", __FILE__, __LINE__));
#endif
} // FindNecessaryInteriorSupernodes()

/// routine to add the necessary interior supernodes to the boundary array
///
/// Side effects: This function updates:
/// - this->NumNecessary
/// - this->BoundaryIndices,
/// - this->BoundaryVertexSuperset
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType,
                       MeshBoundaryExecObjType>::AugmentBoundaryWithNecessaryInteriorSupernodes()
{ // AugmentBoundaryWithNecessaryInteriorSupernodes
  //  1.  Collect the necessary supernodes & boundary vertices & suppress duplicates
  vtkm::worklet::contourtree_augmented::IdArrayType isNecessaryAndInterior;
  vtkm::cont::Algorithm::Copy(this->InteriorForestData->IsNecessary, isNecessaryAndInterior);

  //  a.  To do this, first *UNSET* the necessary flags for all supernodes that are also on the boundary
  auto unsetBoundarySupernodesWorklet =
    bract_maker::AugmentBoundaryWithNecessaryInteriorSupernodesUnsetBoundarySupernodesWorklet();
  this->Invoke(unsetBoundarySupernodesWorklet,
               this->BoundaryIndices,          // input
               this->ContourTree.Superparents, // input
               this->ContourTree.Supernodes,   // input
               isNecessaryAndInterior          // output
  );

#ifdef DEBUG_PRINT
  DebugPrint("Flags Unset", __FILE__, __LINE__);
#endif

  //  b.  Now append all necessary supernodes to the boundary vertex array
  // first count how many are needed, then resize the arrays
  this->NumNecessary =
    vtkm::cont::Algorithm::Reduce(isNecessaryAndInterior, static_cast<vtkm::Id>(0));
  // We need to grow the arrays, without loosing our original data, so we need
  // to create new arrays of the approbriate size, copy our data in and then
  // assign. TODO: Would be great if VTKm allow resize without losing the values
  if (this->NumNecessary == 0)
  {
#ifdef DEBUG_PRINT
    VTKM_LOG_S(vtkm::cont::LogLevel::Info,
               "BoundaryTreeMaker::AugmentBoundaryWithNecessaryInteriorSupernodes():"
               "No additional nodes necessary. Returning.");
#endif
    return;
  }
  {
    // Create a new resized array and copy the original values to the array
    vtkm::worklet::contourtree_augmented::IdArrayType tempBoundaryVertexSuperset;
    tempBoundaryVertexSuperset.Allocate(this->NumBoundary + this->NumNecessary);
    vtkm::cont::Algorithm::CopySubRange(
      this->BoundaryVertexSuperset, // input
      0,                            // start index
      this->NumBoundary, // number of values to copy. Same as size of this->BoundaryVertexSuperset
      tempBoundaryVertexSuperset, // copy to temp
      0                           // start inserting at 0
    );
    // Set the added values to 0
    // TODO: Check if it is really necessary to initalize the new values
    vtkm::cont::Algorithm::CopySubRange(
      vtkm::cont::make_ArrayHandleConstant<vtkm::Id>(0, this->NumBoundary), // set to 0
      0,                                                                    // start index
      this->NumNecessary,         // number of values to copy
      tempBoundaryVertexSuperset, // copy to temp
      this->NumBoundary           // start where our original values end
    );
    // Set the BoundaryVertexSuperset to our new array
    this->BoundaryVertexSuperset.ReleaseResources();
    this->BoundaryVertexSuperset = tempBoundaryVertexSuperset;
  }
  // Now do the same for the BoundaryIndicies (i.e,. resize while keeping the original values)
  {
    // Create a new resized array and copy the original values to the array
    vtkm::worklet::contourtree_augmented::IdArrayType tempBoundaryIndices;
    tempBoundaryIndices.Allocate(this->NumBoundary + this->NumNecessary);
    vtkm::cont::Algorithm::CopySubRange(
      this->BoundaryIndices, // input
      0,                     // start index
      this->NumBoundary,     // number of values to copy. Same as size of this->BoundaryIndices
      tempBoundaryIndices,   // copy to temp
      0                      // start inserting at 0
    );
    // Set the added values to 0
    // TODO: Check if it is really necessary to initalize the new values
    vtkm::cont::Algorithm::CopySubRange(
      vtkm::cont::make_ArrayHandleConstant<vtkm::Id>(0, this->NumBoundary), // set to 0
      0,                                                                    // start index
      this->NumNecessary,  // number of values to copy
      tempBoundaryIndices, // copy to temp
      this->NumBoundary    // start where our original values end
    );
    // Set the BoundaryVertexSuperset to our new array
    this->BoundaryIndices.ReleaseResources();
    this->BoundaryIndices = tempBoundaryIndices;
  }

  // create a temporary array for transfer IDs
  vtkm::worklet::contourtree_augmented::IdArrayType boundaryNecessaryId;
  boundaryNecessaryId.Allocate(this->ContourTree.Supernodes.GetNumberOfValues());
  // and partial sum them in place
  vtkm::cont::Algorithm::ScanInclusive(isNecessaryAndInterior, boundaryNecessaryId);

  // now do a parallel for loop to copy them
  auto appendNecessarySupernodesWorklet =
    bract_maker::AugmentBoundaryWithNecessaryInteriorSupernodesAppendNecessarySupernodesWorklet(
      this->NumBoundary);
  this->Invoke(appendNecessarySupernodesWorklet,
               this->ContourTree.Supernodes,
               isNecessaryAndInterior,
               boundaryNecessaryId,
               this->Mesh->SortOrder,
               this->BoundaryIndices,
               this->BoundaryVertexSuperset);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Necessary Appended", __FILE__, __LINE__));
#endif
} // AugmentBoundaryWithNecessaryInteriorSupernodes


/// routine that sorts on hyperparent to find BRACT superarcs
///
///  Side-effects: This function updates:
///  - this->TreeToSuperset
///  - this->BoundaryIndices
///  - this->BoundaryVertexSuperset
///  - this->BoundaryTreeData->Superarcs
///  - this->BoundaryTreeId
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::FindBoundaryTreeSuperarcs()
{ // FindBoundaryTreeSuperarcs
  //  0.  Allocate memory for the tree2superset map
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                         this->ContourTree.Supernodes.GetNumberOfValues()),
    this->TreeToSuperset);

  //  1.  Sort the boundary set by hyperparent
  auto contourTreeNodeHyperArcComparator = bract_maker::ContourTreeNodeHyperArcComparator(
    this->ContourTree.Superarcs, this->ContourTree.Superparents);
  vtkm::cont::Algorithm::Sort(this->BoundaryIndices, contourTreeNodeHyperArcComparator);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Sorted by Superparent", __FILE__, __LINE__));
#endif

  //  2.  Reset the order of the vertices in the BRACT
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandlePermutation(this->BoundaryIndices, // index array to permute with
                                            this->Mesh->SortOrder  // value array to be permuted
                                            ),
    this->BoundaryVertexSuperset // Copy the permuted Mesh->SortOrder to BoundaryVertexSuperset
  );
#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info, this->DebugPrint("Vertices Reset", __FILE__, __LINE__));
#endif

  // allocate memory for the superarcs (same size as supernodes for now)
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                         this->BoundaryVertexSuperset.GetNumberOfValues()),
    this->BoundaryTreeData->Superarcs);

  // We would like to connect vertices to their neighbour on the hyperarc as usual
  // The problem here is that the root of the tree may be unnecessary
  // and if that is the case we will need to adjust

  // The test will be:
  //     i.  if there is a "next", we will take it
  //    ii.  if we are dangling at the end of the hyperarc, two possibilities exist
  //      a.  the supernode target of the hyperarc is in the BRACT anyway
  //      b.  the supernode target is not in the BRACT

  // To resolve all this, we will need to have an array the size of all the regular nodes
  // in order to find the boundary ID of each vertex transferred
  {
    // Allocate this->BoundaryTreeId with NoSuchElemet
    vtkm::cont::Algorithm::Copy(
      vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                           this->ContourTree.Nodes.GetNumberOfValues()),
      this->BoundaryTreeId);
    // Fill the relevant values
    auto tempPermutedBoundaryTreeId =
      vtkm::cont::make_ArrayHandlePermutation(this->BoundaryVertexSuperset, // index array
                                              this->BoundaryTreeId          // value array
      );
    vtkm::cont::Algorithm::Copy(
      vtkm::cont::ArrayHandleIndex(
        this->BoundaryVertexSuperset.GetNumberOfValues()), // copy 0,1, ... n
      tempPermutedBoundaryTreeId // copy to BoundaryTreeId permuted by BoundaryTreeVertexSuperset
    );
  }

  // We now compute the superarc "to" for every bract node
  auto superarcToWorklet = bract_maker::FindBoundaryTreeSuperarcsSuperarcToWorklet();
  this->Invoke(superarcToWorklet,
               this->BoundaryVertexSuperset,     // input
               this->BoundaryIndices,            // input
               this->BoundaryTreeId,             // input
               this->ContourTree.Superparents,   // input
               this->ContourTree.Hyperparents,   // input
               this->ContourTree.Hyperarcs,      // input
               this->ContourTree.Supernodes,     // input
               this->Mesh->SortOrder,            // input
               this->TreeToSuperset,             // output
               this->BoundaryTreeData->Superarcs // output
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Restricted to Boundary", __FILE__, __LINE__));
#endif
} // FindBoundaryTreeSuperarcs


/// compresses out supernodes in the interior that have become regular in the BRACT
///
/// Side-effects: This function has the cummulative side effects of
/// - this->SetUpAndDownNeighbours();
/// - this->IdentifyRegularisedSupernodes();
/// - this->AddTerminalFlagsToUpDownNeighbours();
/// - this->PointerDoubleUpDownNeighbours();
/// - this->CompressRegularisedNodes();
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::SuppressRegularisedInteriorSupernodes()
{ // SuppressRegularisedInteriorSupernodes
  // 1.   We have to suppress regular vertices that were interior critical points
  //      We can't get rid of them earlier because we need them to connect super-/hyper- arcs

  //  STEP I:    Find a (non-canonical) up/down neighbour for each vertex
  SetUpAndDownNeighbours();

  //  STEP II:  Find the critical points
  IdentifyRegularisedSupernodes();

  //  STEP III:  Set flags to indicate which pointers are terminal
  AddTerminalFlagsToUpDownNeighbours();

  //  STEP IV:  Use pointer-doubling to collapse past regular nodes
  PointerDoubleUpDownNeighbours();

  //  STEP V:    Get rid of the now regular interior supernodes
  CompressRegularisedNodes();
} // SuppressRegularisedInteriorSupernodes


/// routine to find *AN* up/down neighbour for each vertex
/// this is deliberately non-canonical and exploits write-conflicts
///
/// Side effect: This function updates
/// - this->UpNeighbour
/// - this->DownNeighbour
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::SetUpAndDownNeighbours()
{ // SetUpAndDownNeighbours
  //  So, we will set an up- and down-neighbour for each one (for critical points, non-canonical)
  { // local context
    auto tempNoSuchElementArray =
      vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                           this->BoundaryVertexSuperset.GetNumberOfValues());
    vtkm::cont::Algorithm::Copy(tempNoSuchElementArray, this->UpNeighbour);
    vtkm::cont::Algorithm::Copy(tempNoSuchElementArray, this->DownNeighbour);
  } // end local context

  auto setUpAndDownNeighboursWorklet = bract_maker::SetUpAndDownNeighboursWorklet();
  this->Invoke(setUpAndDownNeighboursWorklet,
               this->BoundaryVertexSuperset,      // input
               this->BoundaryTreeData->Superarcs, // input
               this->Mesh->SortIndices,           // input
               this->UpNeighbour,                 // output
               this->DownNeighbour                // output
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Initial Up/Down Neighbours Set", __FILE__, __LINE__));
#endif
} // SetUpAndDownNeighbours


/// routine to set a flag for each vertex that has become regular in the interior of the BRACT
///
/// Side effect: This function updates
/// - this->NewVertexId
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::IdentifyRegularisedSupernodes()
{ // IdentifyRegularisedSupernodes
  // here, if any edge detects the up/down neighbours mismatch, we must have had a write conflict
  // it then follows we have a critical point and can set a flag accordingly
  // we will use a vector that stores NO_SUCH_ELEMENT for false, anything else for true
  // it gets reused as an ID
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                         this->BoundaryVertexSuperset.GetNumberOfValues()),
    this->NewVertexId);

  auto stepOneWorklet = bract_maker::IdentifyRegularisedSupernodesStepOneWorklet();
  this->Invoke(stepOneWorklet,
               this->BoundaryVertexSuperset,      // input
               this->BoundaryTreeData->Superarcs, // input
               this->Mesh->SortIndices,           // input
               this->UpNeighbour,                 // input
               this->DownNeighbour,               // input
               this->NewVertexId                  // output
  );

  //  c.  We also want to flag the leaves and boundary nodes as necessary
  auto stepTwoWorklet = bract_maker::IdentifyRegularisedSupernodesStepTwoWorklet();
  this->Invoke(stepTwoWorklet,
               this->BoundaryVertexSuperset,      // input
               this->UpNeighbour,                 // input
               this->DownNeighbour,               // input
               this->MeshBoundaryExecutionObject, // input
               this->NewVertexId                  // output
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Boundaries & Leaves Set", __FILE__, __LINE__));
#endif
} // IdentifyRegularisedSupernodes


/// this routine sets a flag on every up/down neighbour that points to a critical point
/// to force termination of pointer-doubling
///
/// Side effects: This function updates
/// - this->UpNeighbour
/// - this->DownNeighbour
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::AddTerminalFlagsToUpDownNeighbours()
{ //
  //  d.  Now that we know which vertices are necessary, we can set the upNeighbour & downNeighbour flags
  auto addTerminalFlagsToUpDownNeighboursWorklet =
    bract_maker::AddTerminalFlagsToUpDownNeighboursWorklet();
  this->Invoke(addTerminalFlagsToUpDownNeighboursWorklet,
               this->NewVertexId,  // input
               this->UpNeighbour,  // output
               this->DownNeighbour // output
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Up/Down Neighbours Terminated", __FILE__, __LINE__));
#endif
} //


/// routine that uses pointer-doubling to collapse regular nodes in the BRACT
///
/// Side effects: This functions updates
/// - this->UpNeighbour,
/// - this->DownNeighbour
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::PointerDoubleUpDownNeighbours()
{ // PointerDoubleUpDownNeighbours
  // e. Use pointer-doubling to eliminate regular nodes
  // 1. Compute the number of log steps
  vtkm::Id nLogSteps = 1;
  for (vtkm::Id shifter = this->BoundaryVertexSuperset.GetNumberOfValues(); shifter != 0;
       shifter >>= 1)
  {
    nLogSteps++;
  }

  //  2. loop that many times to do the compression
  for (vtkm::Id iteration = 0; iteration < nLogSteps; iteration++)
  { // per iteration
    // loop through the vertices, updating both ends
    auto pointerDoubleUpDownNeighboursWorklet = bract_maker::PointerDoubleUpDownNeighboursWorklet();
    this->Invoke(pointerDoubleUpDownNeighboursWorklet, this->UpNeighbour, this->DownNeighbour);
  } // per iteration
#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Pointer Doubling Done", __FILE__, __LINE__));
#endif
} // PointerDoubleUpDownNeighbours


/// routine that compresses the regular nodes out of the BRACT
///
/// Side effects: This function updates:
///  - this->NewVertexId
///  - this->NumKept
///  - this->BoundaryTreeData->VertexIndex
///  - this->BoundaryTreeData->Superarcs
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::CompressRegularisedNodes()
{ // CompressRegularisedNodes
  //  f.  Compress the regular nodes out of the tree
  //  1.  Assign new indices
  //      Copy the necessary ones only - this is a compression call in parallel
  // HAC: to use a different array, keeping isNecessary indexed on everything
  //      We now reset it to the size of the return tree
  vtkm::worklet::contourtree_augmented::IdArrayType keptInBoundaryTree;
  //    Start by creating the ID #s with a partial sum (these will actually start from 1, not 0
  vtkm::cont::Algorithm::ScanInclusive(
    vtkm::cont::make_ArrayHandleTransform(this->NewVertexId, bract_maker::NoSuchElementFunctor()),
    keptInBoundaryTree);
  // Update newVertexID, i.e., for each element set:
  // if (!noSuchElement(newVertexID[returnIndex]))
  //      newVertexID[returnIndex] = keptInBoundaryTree[returnIndex]-1;
  auto copyNecessaryRegularNodesWorklet =
    bract_maker::CompressRegularisedNodesCopyNecessaryRegularNodesWorklet();
  this->Invoke(copyNecessaryRegularNodesWorklet,
               this->NewVertexId, // input/output
               keptInBoundaryTree // input
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Compressed IDs Computed", __FILE__, __LINE__));
#endif
  //    2.  Work out the new superarcs, which is slightly tricky, since they point inbound.
  //      For each necessary vertex N, the inbound vertex I in the original contour tree can point to:
  //      i.    Another necessary vertex (in which case we keep the superarc)
  //      ii.    Nothing (in the case of the root) - again, we keep it, since we already know it's necessar
  //      iii.  An unnecessary vertex (i.e. any other case).  Here, the treatment is more complex.
  //          In this case, we know that the pointer-doubling has forced the up/down neighbours of I
  //          to point to necessary vertices (or nothing).  And since we know that there is an inbound
  //          edge from N, N must therefore be either the up or down neighbour of I after doubling.
  //          We therefore go the other way to find which necessary vertex V that N must connect to.
  //
  //      Just to make it interesting, the root vertex can become unnecessary. If this is the case, we end up with two
  //       necessary vertices each with a superarc to the other.  In serial, we can deal with this by checking to see whether
  //      the far end has already been set to ourself, but this doesn't parallelise properly.  So we will have an extra pass
  //      just to get this correct.
  //  We will compute the new superarcs directly with the new size
  //
  //  To do this in parallel, we compute for every vertex

  // first create the array: start by observing that the last entry is guaranteed
  // to hold the total number of necessary vertices
  this->NumKept =
    vtkm::cont::ArrayGetValue(keptInBoundaryTree.GetNumberOfValues() - 1, keptInBoundaryTree);
  // create an array to store the new superarc Ids and initalize it with NO_SUCH_ELEMENT
  vtkm::worklet::contourtree_augmented::IdArrayType newSuperarc;
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                         this->NumKept),
    newSuperarc);
  auto findNewSuperarcsWorklet = bract_maker::CompressRegularisedNodesFindNewSuperarcsWorklet();
  this->Invoke(findNewSuperarcsWorklet,
               this->NewVertexId,                 //input
               this->BoundaryTreeData->Superarcs, //input
               this->UpNeighbour,                 //input
               this->DownNeighbour,               //input
               newSuperarc                        //output
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("New Superarcs Found", __FILE__, __LINE__));
#endif

  //  3.  Now do the pass to resolve the root: choose the direction with decreasing index
  auto resolveRootWorklet = bract_maker::CompressRegularisedNodesResolveRootWorklet();
  this->Invoke(resolveRootWorklet,
               vtkm::cont::ArrayHandleIndex(this->NumKept), // input
               newSuperarc                                  // output
  );

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info, this->DebugPrint("Root Resolved", __FILE__, __LINE__));
#endif

  // 4.  Now transfer the vertices & resize
  vtkm::worklet::contourtree_augmented::IdArrayType newVertexIndex;
  newVertexIndex.Allocate(this->NumKept);
  auto transferVerticesWorklet = bract_maker::CompressRegularisedNodesTransferVerticesWorklet();
  this->Invoke(transferVerticesWorklet,
               this->BoundaryVertexSuperset, // input
               this->NewVertexId,            // input
               newVertexIndex                // output
  );

  //vtkm::worklet::contourtree_augmented::PrintHeader(newVertexIndex.GetNumberOfValues(), std::cerr);
  //vtkm::worklet::contourtree_augmented::PrintIndices("New Vertex Index", newVertexIndex, -1, std::cerr);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Vertices Transferred", __FILE__, __LINE__));
#endif

  // 5.  Create an index array and sort it indirectly by sortOrder
  vtkm::worklet::contourtree_augmented::IdArrayType vertexSorter;
  vtkm::cont::Algorithm::Copy(vtkm::cont::ArrayHandleIndex(this->NumKept), vertexSorter);
  auto bractNodeComparator =
    bract_maker::BoundaryTreeNodeComparator(newVertexIndex, this->Mesh->SortIndices);
  vtkm::cont::Algorithm::Sort(vertexSorter, bractNodeComparator);
  // 5.1. Compute the reverseSorter
  vtkm::worklet::contourtree_augmented::IdArrayType reverseSorter;
  reverseSorter.Allocate(
    this->NumKept); // Reserve space since we need to permute the array for copy
  {
    auto permutedReverseSorter =
      vtkm::cont::make_ArrayHandlePermutation(vertexSorter, reverseSorter);
    vtkm::cont::Algorithm::Copy(vtkm::cont::ArrayHandleIndex(this->NumKept), permutedReverseSorter);
  }

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Indirect Sort Complete", __FILE__, __LINE__));
#endif

  //vtkm::worklet::contourtree_augmented::PrintHeader(vertexSorter.GetNumberOfValues(), std::cerr);
  //vtkm::worklet::contourtree_augmented::PrintIndices("Vertex Sorter", vertexSorter, -1, std::cerr);
  //vtkm::worklet::contourtree_augmented::PrintIndices("Reverse Sorter", reverseSorter, -1, std::cerr);

  //    6.  Resize both vertex IDs and superarcs, and copy in by sorted order
  // copy the vertex index with indirection, and using the sort order NOT the regular ID
  // the following Copy operation is equivilant to
  // bract->vertexIndex[bractID] = mesh->SortIndex(newVertexIndex[vertexSorter[bractID]]);
  vtkm::cont::Algorithm::Copy(
    vtkm::cont::make_ArrayHandlePermutation(
      vtkm::cont::make_ArrayHandlePermutation(vertexSorter, newVertexIndex),
      this->Mesh->SortIndices),
    this->BoundaryTreeData->VertexIndex);

  // now copy the this->BoundaryTreeData->Superarcs
  this->BoundaryTreeData->Superarcs.Allocate(this->NumKept, vtkm::CopyFlag::On);
  auto fillBoundaryTreeSuperarcsWorklet =
    bract_maker::CompressRegularisedNodesFillBoundaryTreeSuperarcsWorklet();
  this->Invoke(fillBoundaryTreeSuperarcsWorklet,
               newSuperarc,
               reverseSorter,
               vertexSorter,
               this->BoundaryTreeData->Superarcs);

#ifdef DEBUG_PRINT
  VTKM_LOG_S(vtkm::cont::LogLevel::Info,
             this->DebugPrint("Regularised Nodes Compressed", __FILE__, __LINE__));
#endif
} // CompressRegularisedNodes


/// sets the arrays in the InteriorForest (i.e., the residue) that need to be passed to the grafting stage. In the original this function was called  SetResidue()
///
/// Side effects: This function updates:
/// - this->InteriorForestData->Above
/// - this->InteriorForestData->Below
/// - this->InteriorForestData->BoundaryTreeMeshIndices
template <typename MeshType, typename MeshBoundaryExecObjType>
void BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::SetInteriorForest(
  const vtkm::worklet::contourtree_augmented::mesh_dem::IdRelabeler* localToGlobalIdRelabeler)
{ // SetInteriorForest()
  // allocate memory for the residue arrays
  auto tempNoSuchElementArray =
    vtkm::cont::make_ArrayHandleConstant(vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT,
                                         this->ContourTree.Supernodes.GetNumberOfValues());
  vtkm::cont::Algorithm::Copy(tempNoSuchElementArray, this->InteriorForestData->Above);
  vtkm::cont::Algorithm::Copy(tempNoSuchElementArray, this->InteriorForestData->Below);

  // now fill them in
  auto meshGlobalIds =
    this->Mesh
      ->template GetGlobalIdsFromMeshIndices<vtkm::worklet::contourtree_augmented::IdArrayType>(
        this->BoundaryVertexSuperset, localToGlobalIdRelabeler);
  auto setInteriorForestWorklet =
    vtkm::worklet::contourtree_distributed::bract_maker::SetInteriorForestWorklet();
  // NOTE: We don't need this->BoundaryVertexSuperset as input since meshGlobalIds is already transformed accordingly
  this->Invoke(setInteriorForestWorklet,
               this->ContourTree.Supernodes,          // input
               this->InteriorForestData->IsNecessary, // input
               this->TreeToSuperset,                  // input,
               meshGlobalIds,                         // input
               this->UpNeighbour,                     // input
               this->DownNeighbour,                   // input
               this->InteriorForestData->Above,       // output
               this->InteriorForestData->Below        // output
  );

  // now copy the mesh indices of the BRACT's vertices for the TreeGrafter to use
  InteriorForestData->BoundaryTreeMeshIndices.Allocate(
    this->BoundaryTreeData->VertexIndex.GetNumberOfValues());
  // per vertex in the bract, convert it to a sort ID and then mesh ID and copy to the InteriorForestData
  vtkm::cont::Algorithm::Copy(vtkm::cont::make_ArrayHandlePermutation(
                                this->BoundaryTreeData->VertexIndex, this->Mesh->SortOrder),
                              InteriorForestData->BoundaryTreeMeshIndices);
} // SetInteriorForest()



/// prints the contents of the restrictor object in a standard format
template <typename MeshType, typename MeshBoundaryExecObjType>
std::string BoundaryTreeMaker<MeshType, MeshBoundaryExecObjType>::DebugPrint(const char* message,
                                                                             const char* fileName,
                                                                             long lineNum) const
{ // DebugPrint
  std::stringstream resultStream;
  resultStream << std::setw(30) << std::left << fileName << ":" << std::right << std::setw(4)
               << lineNum << " ";
  resultStream << std::left << std::string(message) << std::endl;

  resultStream << "------------------------------------------------------" << std::endl;
  resultStream << "BRACT Contains:                                       " << std::endl;
  resultStream << "------------------------------------------------------" << std::endl;
  vtkm::worklet::contourtree_augmented::PrintHeader(
    this->BoundaryTreeData->VertexIndex.GetNumberOfValues(), resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "BRACT Vertices", this->BoundaryTreeData->VertexIndex, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "BRACT Superarcs", this->BoundaryTreeData->Superarcs, -1, resultStream);
  resultStream << "------------------------------------------------------" << std::endl;
  resultStream << "BRACT Maker Contains:                                 " << std::endl;
  resultStream << "------------------------------------------------------" << std::endl;
  resultStream << "nBoundary:  " << this->NumBoundary << std::endl;
  resultStream << "nNecessary: " << this->NumNecessary << std::endl;

  // Regular Vertex Arrays
  vtkm::worklet::contourtree_augmented::PrintHeader(this->BoundaryTreeId.GetNumberOfValues(),
                                                    resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "ID in Boundary Tree", this->BoundaryTreeId, -1, resultStream);
  resultStream << std::endl;

  // Boundary Vertex Arrays
  vtkm::worklet::contourtree_augmented::PrintHeader(this->BoundaryIndices.GetNumberOfValues(),
                                                    resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Boundary Sort Indices", this->BoundaryIndices, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Boundary Vertex Superset", this->BoundaryVertexSuperset, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Boundary Superparents", this->BoundarySuperparents, -1, resultStream);
  resultStream << std::endl;

  // Per Supernode Arrays
  vtkm::worklet::contourtree_augmented::PrintHeader(
    this->SupernodeTransferBoundaryCount.GetNumberOfValues(), resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Supernode Transfer Count", this->SupernodeTransferBoundaryCount, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Superarc Intrinsic Count", this->SuperarcIntrinsicBoundaryCount, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Superarc Dependent Count", this->SuperarcDependentBoundaryCount, -1, resultStream);
  // Print IsNecessary as bool
  vtkm::worklet::contourtree_augmented::PrintValues(
    "isNecessary", this->InteriorForestData->IsNecessary, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Tree To Superset", this->TreeToSuperset, -1, resultStream);
  resultStream << std::endl;

  // Per Hypernode Arrays
  vtkm::worklet::contourtree_augmented::PrintHeader(
    this->HyperarcDependentBoundaryCount.GetNumberOfValues(), resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Hyperarc Dependent Count", this->HyperarcDependentBoundaryCount, -1, resultStream);
  resultStream << std::endl;

  // BRACT sized Arrays
  vtkm::worklet::contourtree_augmented::PrintHeader(this->NewVertexId.GetNumberOfValues(),
                                                    resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "New Vertex ID", this->NewVertexId, -1, resultStream);

  // arrays with double use & different sizes
  vtkm::worklet::contourtree_augmented::PrintHeader(this->UpNeighbour.GetNumberOfValues(),
                                                    resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Up Neighbour", this->UpNeighbour, -1, resultStream);
  vtkm::worklet::contourtree_augmented::PrintIndices(
    "Down Neighbour", this->DownNeighbour, -1, resultStream);

  resultStream << "------------------------------------------------------" << std::endl;
  resultStream << std::endl;
  resultStream << std::flush;
  return resultStream.str();
} // DebugPrint


} // namespace contourtree_distributed
} // namespace worklet
} // namespace vtkm

#endif
