Exemple #1
0
void
NodalFloodCount::mergeSets()
{
  Moose::perf_log.push("mergeSets()", "NodalFloodCount");
  std::set<unsigned int> set_union;
  std::insert_iterator<std::set<unsigned int> > set_union_inserter(set_union, set_union.begin());

  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
  {
    std::list<BubbleData>::iterator end = _bubble_sets[map_num].end();
    for (std::list<BubbleData>::iterator it1 = _bubble_sets[map_num].begin(); it1 != end; /* No increment */)
    {
      bool need_it1_increment = true;

      for (std::list<BubbleData>::iterator it2 = it1; it2 != end; ++it2)
      {
        if (it1 != it2 &&                       // Make sure that these iterators aren't pointing at the same set
            it1->_var_idx == it2->_var_idx &&   // and that the sets have matching variable indices...
                                                // then See if they overlap
            setsIntersect(it1->_nodes.begin(), it1->_nodes.end(), it2->_nodes.begin(), it2->_nodes.end()))
        {
          // Merge these two sets and remove the duplicate set
          set_union.clear();
          std::set_union(it1->_nodes.begin(), it1->_nodes.end(), it2->_nodes.begin(), it2->_nodes.end(), set_union_inserter);

          // Put the merged set in the latter iterator so that we'll compare earlier sets to it again
          it2->_nodes = set_union;
          _bubble_sets[map_num].erase(it1++);

          // don't increment the outer loop since we just deleted it incremented
          need_it1_increment = false;
          // break out of the inner loop and move on
          break;
        }
      }

      if (need_it1_increment)
        ++it1;
    }
  }
  Moose::perf_log.pop("mergeSets()", "NodalFloodCount");
}
void
FeatureFloodCount::calculateBubbleVolumes()
{
  Moose::perf_log.push("calculateBubbleVolume()", "FeatureFloodCount");

  // Figure out which bubbles intersect the boundary if the user has enabled that capability.
  if (_compute_boundary_intersecting_volume)
  {
    // Create a std::set of node IDs which are on the boundary called all_boundary_node_ids.
    std::set<dof_id_type> all_boundary_node_ids;

    // Iterate over the boundary nodes, putting them into the std::set data structure
    MooseMesh::bnd_node_iterator
      boundary_nodes_it  = _mesh.bndNodesBegin(),
      boundary_nodes_end = _mesh.bndNodesEnd();
    for (; boundary_nodes_it != boundary_nodes_end; ++boundary_nodes_it)
    {
      BndNode * boundary_node = *boundary_nodes_it;
      all_boundary_node_ids.insert(boundary_node->_node->id());
    }

    // For each of the _maps_size BubbleData lists, determine if the set
    // of nodes includes any boundary nodes.
    for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    {
      std::list<BubbleData>::iterator
        bubble_it = _bubble_sets[map_num].begin(),
        bubble_end = _bubble_sets[map_num].end();

      // Determine boundary intersection for each BubbleData object
      for (; bubble_it != bubble_end; ++bubble_it)
        bubble_it->_intersects_boundary = setsIntersect(all_boundary_node_ids.begin(), all_boundary_node_ids.end(),
                                                        bubble_it->_entity_ids.begin(), bubble_it->_entity_ids.end());
    }
  }

  // Size our temporary data structure
  std::vector<std::vector<Real> > bubble_volumes(_maps_size);
  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    bubble_volumes[map_num].resize(_bubble_sets[map_num].size());

  // Clear pre-existing values and allocate space to store the volume
  // of the boundary-intersecting grains for each variable.
  _total_volume_intersecting_boundary.clear();
  _total_volume_intersecting_boundary.resize(_maps_size);

  // Loop over the active local elements.  For each variable, and for
  // each BubbleData object, check whether a majority of the element's
  // nodes belong to that Bubble, and if so assign the element's full
  // volume to that bubble.
  const MeshBase::const_element_iterator el_end = _mesh.getMesh().active_local_elements_end();
  for (MeshBase::const_element_iterator el = _mesh.getMesh().active_local_elements_begin(); el != el_end; ++el)
  {
    Elem * elem = *el;
    unsigned int elem_n_nodes = elem->n_nodes();
    Real curr_volume = elem->volume();

    for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    {
      std::list<BubbleData>::const_iterator
        bubble_it = _bubble_sets[map_num].begin(),
        bubble_end = _bubble_sets[map_num].end();

      for (unsigned int bubble_counter = 0; bubble_it != bubble_end; ++bubble_it, ++bubble_counter)
      {
        // Count the number of nodes on this element which are flooded.
        unsigned int flooded_nodes = 0;
        for (unsigned int node = 0; node < elem_n_nodes; ++node)
        {
          dof_id_type node_id = elem->node(node);
          if (bubble_it->_entity_ids.find(node_id) != bubble_it->_entity_ids.end())
            ++flooded_nodes;
        }

        // If a majority of the nodes for this element are flooded,
        // assign its volume to the current bubble_counter entry.
        if (flooded_nodes >= elem_n_nodes / 2)
        {
          bubble_volumes[map_num][bubble_counter] += curr_volume;

          // If the current bubble also intersects the boundary, also
          // accumlate the volume into the total volume of bubbles
          // which intersect the boundary.
          if (bubble_it->_intersects_boundary)
            _total_volume_intersecting_boundary[map_num] += curr_volume;
        }
      }
    }
  }

  // If we're calculating boundary-intersecting volumes, we have to normalize it by the
  // volume of the entire domain.
  if (_compute_boundary_intersecting_volume)
  {
    // Compute the total area using a bounding box.  FIXME: this
    // assumes the domain is rectangular and 2D, and is probably a
    // little expensive so we should only do it once if possible.
    MeshTools::BoundingBox bbox = MeshTools::bounding_box(_mesh);
    Real total_volume = (bbox.max()(0)-bbox.min()(0))*(bbox.max()(1)-bbox.min()(1));

    // Sum up the partial boundary grain volume contributions from all processors
    _communicator.sum(_total_volume_intersecting_boundary);

    // Scale the boundary intersecting grain volumes by the total domain volume
    for (unsigned int i = 0; i<_total_volume_intersecting_boundary.size(); ++i)
      _total_volume_intersecting_boundary[i] /= total_volume;
  }

  // Stick all the partial bubble volumes in one long single vector to be gathered on the root processor
  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    _all_bubble_volumes.insert(_all_bubble_volumes.end(), bubble_volumes[map_num].begin(), bubble_volumes[map_num].end());

  // do all the sums!
  _communicator.sum(_all_bubble_volumes);

  std::sort(_all_bubble_volumes.begin(), _all_bubble_volumes.end(), std::greater<Real>());

  Moose::perf_log.pop("calculateBubbleVolume()", "FeatureFloodCount");
}
void
FeatureFloodCount::mergeSets(bool use_periodic_boundary_info)
{
  Moose::perf_log.push("mergeSets()", "FeatureFloodCount");
  std::set<dof_id_type> set_union;
  std::insert_iterator<std::set<dof_id_type> > set_union_inserter(set_union, set_union.begin());

  /**
   * If map_num <= n_processors (normal case), each processor up to map_num will handle one list
   * of nodes and receive the merged nodes from other processors for all other lists.
   */
  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
  {
    unsigned int owner_id = map_num % _app.n_processors();
    if (_single_map_mode || owner_id == processor_id())
    {
      // Get an iterator pointing to the end of the list, we'll reuse it several times in the merge algorithm below
      std::list<BubbleData>::iterator end = _bubble_sets[map_num].end();

      // Next add periodic neighbor information if requested to the BubbleData objects
      if (use_periodic_boundary_info)
        for (std::list<BubbleData>::iterator it = _bubble_sets[map_num].begin(); it != end; ++it)
          appendPeriodicNeighborNodes(*it);

      // Finally start our merge loops
      for (std::list<BubbleData>::iterator it1 = _bubble_sets[map_num].begin(); it1 != end; /* No increment */)
      {
        bool need_it1_increment = true;

        for (std::list<BubbleData>::iterator it2 = it1; it2 != end; ++it2)
        {
          if (it1 != it2 &&                                                               // Make sure that these iterators aren't pointing at the same set
              it1->_var_idx == it2->_var_idx &&                                           // and that the sets have matching variable indices...
              (setsIntersect(it1->_entity_ids.begin(), it1->_entity_ids.end(),            // Do they overlap on the current entity type? OR..
                             it2->_entity_ids.begin(), it2->_entity_ids.end()) ||
                 (use_periodic_boundary_info &&                                           // Are we merging across periodic boundaries? AND
                 setsIntersect(it1->_periodic_nodes.begin(), it1->_periodic_nodes.end(),  // Do they overlap on periodic nodes?
                               it2->_periodic_nodes.begin(), it2->_periodic_nodes.end())
                 )
              )
            )
          {
            // Merge these two entity sets
            set_union.clear();
            std::set_union(it1->_entity_ids.begin(), it1->_entity_ids.end(), it2->_entity_ids.begin(), it2->_entity_ids.end(), set_union_inserter);
            // Put the merged set in the latter iterator so that we'll compare earlier sets to it again
            it2->_entity_ids = set_union;

            // If we are merging periodic boundaries we'll need to merge those nodes too
            if (use_periodic_boundary_info)
            {
              set_union.clear();
              std::set_union(it1->_periodic_nodes.begin(), it1->_periodic_nodes.end(), it2->_periodic_nodes.begin(), it2->_periodic_nodes.end(), set_union_inserter);
              it2->_periodic_nodes = set_union;
            }

            // Now remove the merged set, the one we didn't update (it1)
            _bubble_sets[map_num].erase(it1++);
            // don't increment the outer loop since we just deleted it incremented
            need_it1_increment = false;
            // break out of the inner loop and move on
            break;
          }
        }

        if (need_it1_increment)
          ++it1;
      }
    }
  }

  if (!_single_map_mode)
    for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
      // Now communicate this list with all the other processors
      communicateOneList(_bubble_sets[map_num], map_num % _app.n_processors(), map_num);

  Moose::perf_log.pop("mergeSets()", "FeatureFloodCount");
}
Exemple #4
0
bool
FeatureFloodCount::FeatureData::ghostedIntersect(const FeatureData & rhs) const
{
  return setsIntersect(_ghosted_ids.begin(), _ghosted_ids.end(),
                       rhs._ghosted_ids.begin(), rhs._ghosted_ids.end());
}
Exemple #5
0
bool
FeatureFloodCount::FeatureData::periodicBoundariesIntersect(const FeatureData & rhs) const
{
  return setsIntersect(_periodic_nodes.begin(), _periodic_nodes.end(),
                       rhs._periodic_nodes.begin(), rhs._periodic_nodes.end());
}
Exemple #6
0
bool
FeatureFloodCount::FeatureData::halosIntersect(const FeatureData & rhs) const
{
  return setsIntersect(_halo_ids.begin(), _halo_ids.end(),
                       rhs._halo_ids.begin(), rhs._halo_ids.end());
}
Exemple #7
0
bool
GrainTracker::attemptGrainRenumber(MooseSharedPointer<FeatureData> grain, unsigned int grain_id, unsigned int depth, unsigned int max)
{
  // End the recursion of our breadth first search
  if (depth > max)
    return false;

  unsigned int curr_var_idx = grain->_var_idx;

  std::map<Node *, CacheValues> cache;

  std::vector<std::list<GrainDistance> > min_distances(_vars.size());

  /**
   * We have two grains that are getting close represented by the same order parameter.
   * We need to map to the variable whose closest grain to this one is furthest away by sphere to sphere distance.
   */
  computeMinDistancesFromGrain(grain, min_distances);

  /**
   * We have a vector of the distances to the closest grains represented by each of our variables.  We just need to pick
   * a suitable grain to replace with.  We will start with the maximum of this this list: (max of the mins), but will settle
   * for next to largest and so forth as we make more attempts at remapping grains.  This is a graph coloring problem so
   * more work will be required to optimize this process.
   *
   * Note: We don't have an explicit check here to avoid remapping a  variable to itself.  This is unnecessary since the
   * min_distance of a variable is explicitly set up above.
   */

  std::sort(min_distances.begin(), min_distances.end(), GrainDistanceSorter());

  _console << "\n********************************************\nDistances list for grain " << grain_id << '\n';
  for (unsigned int i = 0; i < min_distances.size(); ++i)
  {
    for (std::list<GrainDistance>::iterator min_it = min_distances[i].begin(); min_it != min_distances[i].end(); ++min_it)
      _console << min_it->_distance << ": " << min_it->_grain_id << ": " <<  min_it->_var_index << '\n';
    _console << '\n';
  }

  for (unsigned int i = 0; i < min_distances.size(); ++i)
  {
    std::list<GrainDistance>::const_iterator target_it = min_distances[i].begin();
    if (target_it == min_distances[i].end())
      continue;

    // If the distance is positive we can just remap and be done
    if (target_it->_distance > 0)
    {
      Moose::out
        << COLOR_GREEN
        << "- Depth " << depth << ": Remapping grain #" << grain_id << " from variable index " << curr_var_idx
        << " to " << target_it->_var_index << " whose closest grain (#" << target_it->_grain_id
        << ") is at a distance of " << target_it->_distance << "\n"
        << COLOR_DEFAULT;

      swapSolutionValues(grain, target_it->_var_index, cache, BYPASS, depth);
      return true;
    }

    // If the distance isn't positive we just need to make sure that none of the grains represented by the
    // target variable index would intersect this one if we were to remap
    std::list<GrainDistance>::const_iterator next_target_it = target_it;
    bool intersection_hit = false;
    std::ostringstream oss;
    while (!intersection_hit && next_target_it != min_distances[i].end())
    {
      if (next_target_it->_distance > 0)
        break;

      mooseAssert(_unique_grains.find(next_target_it->_grain_id) != _unique_grains.end(), "Error in indexing target grain in attemptGrainRenumber");
      MooseSharedPointer<FeatureData> next_target_grain = _unique_grains[next_target_it->_grain_id];

      // If any grains touch we're done here
      if (setsIntersect(grain->_halo_ids.begin(), grain->_halo_ids.end(),
                        next_target_grain->_halo_ids.begin(), next_target_grain->_halo_ids.end()))
        intersection_hit = true;
      else
        oss << " #" << next_target_it->_grain_id;

      ++next_target_it;
    }

    if (!intersection_hit)
    {
      Moose::out
        << COLOR_GREEN
        << "- Depth " << depth << ": Remapping grain #" << grain_id << " from variable index " << curr_var_idx
        << " to " << target_it->_var_index << " whose closest grain(s):" << oss.str()
        << " are inside our bounding sphere but whose halo(s) are not touching.\n"
        << COLOR_DEFAULT;

      swapSolutionValues(grain, target_it->_var_index, cache, BYPASS, depth);
      return true;
    }

    // If we reach this part of the loop, there is no simple renumbering that can be done.
    mooseAssert(_unique_grains.find(target_it->_grain_id) != _unique_grains.end(), "Error in indexing target grain in attemptGrainRenumber");
    MooseSharedPointer<FeatureData> target_grain = _unique_grains[target_it->_grain_id];

    // Make sure this grain isn't marked. If it is, we can't recurse here
    if (target_grain->_merged)
      return false;

    // Save the solution values in case we overright them during recursion
    swapSolutionValues(grain, target_it->_var_index, cache, FILL, depth);

    // TODO: Make sure this distance is -1 or higher or fine intersections only exist for a single variable
    // Propose a new variable index for the current grain and recurse
    grain->_var_idx = target_it->_var_index;
    grain->_merged = true;
    if (attemptGrainRenumber(target_grain, target_it->_grain_id, depth+1, max))
    {
      // SUCCESS!
      Moose::out
        << COLOR_GREEN
        << "- Depth " << depth << ": Remapping grain #" << grain_id << " from variable index " << curr_var_idx
        << " to " << target_it->_var_index << '\n'
        << COLOR_DEFAULT;

      // NOTE: swapSolutionValues currently reads the current variable index off the grain. We need to set
      //       back here before calling this method.
      grain->_var_idx = curr_var_idx;
      swapSolutionValues(grain, target_it->_var_index, cache, USE, depth);

      return true;
    }
    else
      // Need to set our var index back after failed recursive step
      grain->_var_idx = curr_var_idx;

    // Always "unmark" the grain after the recursion so it can be used by other remap operations
    grain->_merged = false;
  }

  return false;
}
Exemple #8
0
void
GrainTracker::remapGrains()
{
  // Don't remap grains if the current simulation step is before the specified tracking step
  if (_t_step < _tracking_step)
    return;

  _console << "Running remap Grains" << std::endl;

  /**
   * The remapping algorithm is recursive. We will reuse the "merged" flag in the FeatureData
   * to track which grains are currently being remapped so we don't have runaway recursion.
   * TODO: Possibly rename this variable?
   */
  for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::iterator grain_it = _unique_grains.begin();
       grain_it != _unique_grains.end(); ++grain_it)
    grain_it->second->_merged = false;

  /**
   * Loop over each grain and see if any grains represented by the same variable are "touching"
   */
  bool grains_remapped;
  do
  {
    grains_remapped = false;
    for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::iterator grain_it1 = _unique_grains.begin();
         grain_it1 != _unique_grains.end(); ++grain_it1)
    {
      if (grain_it1->second->_status == INACTIVE)
        continue;

      for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::iterator grain_it2 = _unique_grains.begin();
           grain_it2 != _unique_grains.end(); ++grain_it2)
      {
        // Don't compare a grain with itself and don't try to remap inactive grains
        if (grain_it1 == grain_it2 || grain_it2->second->_status == INACTIVE)
          continue;

        if (grain_it1->second->_var_idx == grain_it2->second->_var_idx &&   // Are the grains represented by the same variable?
            grain_it1->second->isStichable(*grain_it2->second) &&           // If so, do their bboxes intersect (coarse level check)?
            setsIntersect(grain_it1->second->_halo_ids.begin(),             // If so, do they actually overlap (tight "hull" check)?
                          grain_it1->second->_halo_ids.end(),
                          grain_it2->second->_halo_ids.begin(),
                          grain_it2->second->_halo_ids.end()))
        {
          Moose::out
            << COLOR_YELLOW
            << "Grain #" << grain_it1->first << " intersects Grain #" << grain_it2->first
            << " (variable index: " << grain_it1->second->_var_idx << ")\n"
            << COLOR_DEFAULT;

          for (unsigned int max = 0; max <= _max_renumbering_recursion; ++max)
            if (max < _max_renumbering_recursion)
            {
              if (attemptGrainRenumber(grain_it1->second, grain_it1->first, 0, max) || attemptGrainRenumber(grain_it2->second, grain_it2->first, 0, max))
                break;
            }
            else if (!attemptGrainRenumber(grain_it1->second, grain_it1->first, 0, max) && !attemptGrainRenumber(grain_it2->second, grain_it2->first, 0, max))
              mooseError(COLOR_RED << "Unable to find any suitable grains for remapping. Perhaps you need more op variables?\n\n" << COLOR_DEFAULT);

          grains_remapped = true;
        }
      }
    }
  }
  while (grains_remapped);
}
Exemple #9
0
void
FeatureFloodCount::mergeSets(bool use_periodic_boundary_info)
{
  Moose::perf_log.push("mergeSets()", "FeatureFloodCount");
  std::set<dof_id_type> set_union;

  processor_id_type n_procs = _app.n_processors();

  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
  {
    for (processor_id_type rank1 = 0; rank1 < n_procs; ++rank1)
    {
    REPEAT_MERGE_LOOPS:

      for (processor_id_type rank2 = 0; rank2 < n_procs; ++rank2)
      {
        for (std::vector<FeatureData>::iterator it1 = _partial_feature_sets[rank1][map_num].begin();
             it1 != _partial_feature_sets[rank1][map_num].end(); /* no increment ++it1 */)
        {
          if (it1->_merged)
          {
            ++it1;
            continue;
          }

          bool region_merged = false;
          for (std::vector<FeatureData>::iterator it2 = _partial_feature_sets[rank2][map_num].begin(); it2 != _partial_feature_sets[rank2][map_num].end(); ++it2)
          {
            bool pb_intersect = false;
            if (it1 != it2 &&                                                                // Make sure that these iterators aren't pointing at the same set
                !it2->_merged &&                                                             // and that it2 is not merged (it1 was already checked)
                it1->_var_idx == it2->_var_idx &&                                            // and that the sets have matching variable indices
                ((use_periodic_boundary_info &&                                              // and (if merging across periodic nodes
                   (pb_intersect = setsIntersect(it1->_periodic_nodes.begin(),               //      do those periodic nodes intersect?
                                                 it1->_periodic_nodes.end(),
                                                 it2->_periodic_nodes.begin(),
                                                 it2->_periodic_nodes.end())))
                     ||                                                                      //      or
                   (it1->isStichable(*it2) &&                                                //      if the region bboxes intersect
                     (setsIntersect(it1->_ghosted_ids.begin(),                               //      do those ghosted nodes intersect?)
                                    it1->_ghosted_ids.end(),
                                    it2->_ghosted_ids.begin(),
                                    it2->_ghosted_ids.end()))
                   )
                 )
               )
            {
              /**
               * Even though we've determined that these two partial regions need to be merged, we don't necessarily know if the _ghost_ids intersect.
               * We could be in this branch because the periodic boundaries intersect but that doesn't tell us anything about whether or not the ghost_region
               * also intersects. If the _ghost_ids intersect, that means that we are merging along a periodic boundary, not across one. In this case the
               * bounding box(s) need to be expanded.
               */
              set_union.clear();
              std::set_union(it1->_ghosted_ids.begin(), it1->_ghosted_ids.end(), it2->_ghosted_ids.begin(), it2->_ghosted_ids.end(),
                             std::insert_iterator<std::set<dof_id_type> >(set_union, set_union.begin()));

              // Was there overlap in the physical region?
              bool physical_intersection = (it1->_ghosted_ids.size() + it2->_ghosted_ids.size() > set_union.size());

              it1->_ghosted_ids.swap(set_union);
              it2->_ghosted_ids.clear();

              set_union.clear();
              std::set_union(it1->_periodic_nodes.begin(), it1->_periodic_nodes.end(), it2->_periodic_nodes.begin(), it2->_periodic_nodes.end(),
                             std::insert_iterator<std::set<dof_id_type> >(set_union, set_union.begin()));
              it1->_periodic_nodes.swap(set_union);
              it2->_periodic_nodes.clear();

              set_union.clear();
              std::set_union(it1->_local_ids.begin(), it1->_local_ids.end(), it2->_local_ids.begin(), it2->_local_ids.end(),
                             std::insert_iterator<std::set<dof_id_type> >(set_union, set_union.begin()));
              it1->_local_ids.swap(set_union);
              it2->_local_ids.clear();

              set_union.clear();
              std::set_union(it1->_halo_ids.begin(), it1->_halo_ids.end(), it2->_halo_ids.begin(), it2->_halo_ids.end(),
                             std::insert_iterator<std::set<dof_id_type> >(set_union, set_union.begin()));
              it1->_halo_ids.swap(set_union);
              it2->_halo_ids.clear();

              /**
               * If we had a physical intersection, we need to expand boxes. If we had a virtual (periodic) intersection we need to preserve
               * all of the boxes from each of the regions' sets.
               */
              if (physical_intersection)
                it1->expandBBox(*it2);
              else
                std::copy(it2->_bboxes.begin(), it2->_bboxes.end(), std::back_inserter(it1->_bboxes));

              // Update the min feature id
              it1->_min_entity_id = std::min(it1->_min_entity_id, it2->_min_entity_id);

              // Set the flag on the merged set so we don't revisit it again
              it2->_merged = true;

              // Something was merged so we'll need to repeat this loop
              region_merged = true;
            }
          } // it2 loop

          // Don't increment if we had a merge, we need to retry earlier candidates again
          if (!region_merged)
            ++it1;
          else
            goto REPEAT_MERGE_LOOPS;

        } // it1 loop
      } // rank2 loop
    } // rank1 loop
  } // map loop


  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    _feature_sets[map_num].clear();

  // Now consolidate the data structure
  _feature_count = 0;
  for (processor_id_type rank = 0; rank < n_procs; ++rank)
    for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    {
      for (std::vector<FeatureData>::iterator it = _partial_feature_sets[rank][map_num].begin();
           it != _partial_feature_sets[rank][map_num].end(); ++it)
      {
        if (!it->_merged)
        {
          _feature_sets[map_num].push_back(MooseSharedPointer<FeatureData>(new FeatureData(*it)));
          ++_feature_count;
        }
      }

      _partial_feature_sets[rank][map_num].clear();
    }

  Moose::perf_log.pop("mergeSets()", "FeatureFloodCount");
}