Example #1
0
void
GrainTracker::trackGrains()
{
  // Don't track grains if the current simulation step is before the specified tracking step
  if (_t_step < _tracking_step)
    return;

  // Reset Status on active unique grains
  std::vector<unsigned int> map_sizes(_maps_size);
  for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::iterator grain_it = _unique_grains.begin();
       grain_it != _unique_grains.end(); ++grain_it)
  {
    if (grain_it->second->_status != INACTIVE)
    {
      grain_it->second->_status = NOT_MARKED;
      map_sizes[grain_it->second->_var_idx]++;
    }
  }

  // Print out info on the number of unique grains per variable vs the incoming bubble set sizes
  if (_t_step > _tracking_step)
  {
    bool display_them = false;
    for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    {
      _console << "\nGrains active index " << map_num << ": " << map_sizes[map_num] << " -> " << _feature_sets[map_num].size();
      if (map_sizes[map_num] > _feature_sets[map_num].size())
      {
        _console << "--";
        display_them = true;
      }
      else if (map_sizes[map_num] < _feature_sets[map_num].size())
      {
        _console << "++";
        display_them = true;
      }
    }
    _console << std::endl;
  }

  /**
   * If it's the first time through this routine for the simulation, we will generate the unique grain
   * numbers using a simple counter.  These will be the unique grain numbers that we must track for
   * the remainder of the simulation.
   *
   * If we are linked to the EBSD Reader, we'll get the numbering information from its data
   * rather than generating our own.
   */
  if (_t_step == _tracking_step)   // Start tracking when the time_step == the tracking_step
  {
    if (_ebsd_reader)
    {
      Real grain_num = _ebsd_reader->getGrainNum();

      std::vector<Point> center_points(grain_num);

      for (unsigned int gr = 0; gr < grain_num; ++gr)
      {
        const EBSDReader::EBSDAvgData & d = _ebsd_reader->getAvgData(gr);
        center_points[gr] = d._p;

        Moose::out << "EBSD Grain " << gr << " " << center_points[gr] << '\n';
      }

      // To find the minimum distance we will use the boundingRegionDistance routine.
      std::vector<MeshTools::BoundingBox> ebsd_vector(1);

      std::set<unsigned int> used_indices;
      std::map<unsigned int, unsigned int> error_indices;

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

      if (grain_num != total_grains && processor_id() == 0)
        mooseWarning("Mismatch:\nEBSD centers: " << grain_num << " Grain Tracker Centers: " << total_grains);

      unsigned int next_index = grain_num;

      // Loop over all of the features (grains)
      for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
        for (unsigned int feature_num = 0; feature_num < _feature_sets[map_num].size(); ++feature_num)
        {
          Real min_centroid_diff = std::numeric_limits<Real>::max();
          unsigned int closest_match_idx = 0;

          for (unsigned int j = 0; j < center_points.size(); ++j)
          {
            // Update the ebsd bbox data to be used in the boundingRegionDistance calculation
            // Since we are using centroid matching we'll just make it easy and set both the min/max of the box to the same
            // value (i.e. a zero sized box).
            ebsd_vector[0].min() = ebsd_vector[0].max() = center_points[j];

            Real curr_centroid_diff = boundingRegionDistance(ebsd_vector, _feature_sets[map_num][feature_num]->_bboxes, true);
            if (curr_centroid_diff <= min_centroid_diff)
            {
              closest_match_idx = j;
              min_centroid_diff = curr_centroid_diff;
            }
          }

          if (used_indices.find(closest_match_idx) != used_indices.end())
          {
            Moose::out << "Re-assigning center " << closest_match_idx << " -> " << next_index << " "
                       << center_points[closest_match_idx] << " absolute distance: " << min_centroid_diff << '\n';
            _unique_grains[next_index] = _feature_sets[map_num][feature_num];

            _unique_grain_to_ebsd_num[next_index] = closest_match_idx;

            ++next_index;
          }
          else
          {
            Moose::out << "Assigning center " << closest_match_idx << " "
                       << center_points[closest_match_idx] << " absolute distance: " << min_centroid_diff << '\n';
            _unique_grains[closest_match_idx] = _feature_sets[map_num][feature_num];

            _unique_grain_to_ebsd_num[closest_match_idx] = closest_match_idx;

            used_indices.insert(closest_match_idx);
          }
        }

      if (!error_indices.empty())
      {
        for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::const_iterator grain_it = _unique_grains.begin(); grain_it != _unique_grains.end(); ++grain_it)
          Moose::out << "Grain " << grain_it->first << ": " << center_points[grain_it->first] << '\n';

        Moose::out << "Error Indices:\n";
        for (std::map<unsigned int, unsigned int>::const_iterator it = error_indices.begin(); it != error_indices.end(); ++it)
          Moose::out << "Grain " << it->first << '(' << it->second << ')' << ": " << center_points[it->second] << '\n';

        mooseError("Error with ESBD Mapping (see above unused indices)");
      }
    }
    else // Just assign IDs in order
    {
      unsigned int counter = 0;
      for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
        for (unsigned int feature_num = 0; feature_num < _feature_sets[map_num].size(); ++feature_num)
        {
          MooseSharedPointer<FeatureData> feature_ptr = _feature_sets[map_num][feature_num];

          feature_ptr->_status = MARKED;
          _unique_grains[counter++] = feature_ptr;
        }
    }
    return;  // Return early - no matching or tracking to do
  }

  /**
   * To track grains across time steps, we will loop over our unique grains and link each one up with one of our new
   * unique grains.  The criteria for doing this will be to find the unique grain in the new list with a matching variable
   * index whose centroid is closest to this unique grain.
   */
  std::map<std::pair<unsigned int, unsigned int>, std::vector<unsigned int> > new_grain_idx_to_existing_grain_idx;

  for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::iterator curr_it = _unique_grains.begin(); curr_it != _unique_grains.end(); ++curr_it)
  {
    if (curr_it->second->_status == INACTIVE)                         // Don't try to find matches for inactive grains
      continue;

    unsigned int closest_match_idx;
    bool found_one = false;
    Real min_centroid_diff = std::numeric_limits<Real>::max();

    // We only need to examine grains that have matching variable indices
    unsigned int map_idx = _single_map_mode ? 0 : curr_it->second->_var_idx;
    for (unsigned int new_grain_idx = 0; new_grain_idx < _feature_sets[map_idx].size(); ++new_grain_idx)
    {
      if (curr_it->second->_var_idx == _feature_sets[map_idx][new_grain_idx]->_var_idx)  // Do the variables indicies match?
      {
        Real curr_centroid_diff = boundingRegionDistance(curr_it->second->_bboxes, _feature_sets[map_idx][new_grain_idx]->_bboxes, true);
        if (curr_centroid_diff <= min_centroid_diff)
        {
          found_one = true;
          closest_match_idx = new_grain_idx;
          min_centroid_diff = curr_centroid_diff;
        }
      }
    }

    if (found_one)
      // Keep track of which new grains the existing ones want to map to
      new_grain_idx_to_existing_grain_idx[std::make_pair(map_idx, closest_match_idx)].push_back(curr_it->first);
  }

  /**
   * It's possible that multiple existing grains will map to a single new grain (indicated by multiplicity in the
   * new_grain_idx_to_existing_grain_idx data structure).  This will happen any time a grain disappears during
   * this time step. We need to figure out the rightful owner in this case and inactivate the old grain.
   */
  for (std::map<std::pair<unsigned int, unsigned int>, std::vector<unsigned int> >::iterator it = new_grain_idx_to_existing_grain_idx.begin();
       it != new_grain_idx_to_existing_grain_idx.end(); ++it)
  {
                                                                // map index     feature index
    MooseSharedPointer<FeatureData> feature_ptr = _feature_sets[it->first.first][it->first.second];

    // If there is only a single mapping - we've found the correct grain
    if (it->second.size() == 1)
    {
      unsigned int curr_idx = (it->second)[0];
      feature_ptr->_status = MARKED;                          // Mark it
      _unique_grains[curr_idx] = feature_ptr;                 // transfer ownership of new grain
    }

    // More than one existing grain is mapping to a new one (i.e. multiple values exist for a single key)
    else
    {
      Real min_centroid_diff = std::numeric_limits<Real>::max();
      unsigned int min_idx = 0;

      for (unsigned int i = 0; i < it->second.size(); ++i)
      {
        unsigned int curr_idx = (it->second)[i];

        Real curr_centroid_diff = boundingRegionDistance(feature_ptr->_bboxes, _unique_grains[curr_idx]->_bboxes, true);
        if (curr_centroid_diff <= min_centroid_diff)
        {
          min_idx = i;
          min_centroid_diff = curr_centroid_diff;
        }
      }

      // One more time over the competing indices.  We will mark the non-winners as inactive and transfer ownership to the winner (the closest centroid).
      for (unsigned int i = 0; i < it->second.size(); ++i)
      {
        unsigned int curr_idx = (it->second)[i];
        if (i == min_idx)
        {
          feature_ptr->_status = MARKED;                          // Mark it
          _unique_grains[curr_idx] = feature_ptr;                 // transfer ownership of new grain
        }
        else
        {
          _console << "Marking Grain " << curr_idx << " as INACTIVE (variable index: "
                   << _unique_grains[curr_idx]->_var_idx << ")\n"
                   << *_unique_grains[curr_idx];
          _unique_grains[curr_idx]->_status = INACTIVE;
        }
      }
    }
  }

  /**
   * Next we need to look at our new list and see which grains weren't matched up.  These are new grains.
   */
  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    for (unsigned int i = 0; i < _feature_sets[map_num].size(); ++i)
      if (_feature_sets[map_num][i]->_status == NOT_MARKED)
      {
        _console << COLOR_YELLOW
                 << "*****************************************************************************\n"
                 << "Couldn't find a matching grain while working on variable index: " << _feature_sets[map_num][i]->_var_idx
                 << "\nCreating new unique grain: " << _unique_grains.size() << '\n' <<  *_feature_sets[map_num][i]
                 << "\n*****************************************************************************\n" << COLOR_DEFAULT;
        _feature_sets[map_num][i]->_status = MARKED;
        _unique_grains[_unique_grains.size()] = _feature_sets[map_num][i];   // transfer ownership
      }

  /**
   * Finally we need to mark any grains in the unique list that aren't marked as inactive.  These are the variables that
   * unique grains that didn't match up to any bounding sphere.  Should only happen if it's the last active grain for
   * this particular variable.
   */
  for (std::map<unsigned int, MooseSharedPointer<FeatureData> >::iterator it = _unique_grains.begin(); it != _unique_grains.end(); ++it)
    if (it->second->_status == NOT_MARKED)
    {
      _console << "Marking Grain " << it->first << " as INACTIVE (variable index: "
               << it->second->_var_idx <<  ")\n"
               << *it->second;
      it->second->_status = INACTIVE;
    }
}
Example #2
0
void
GrainTracker::trackGrains()
{
  // Don't track grains if the current simulation step is before the specified tracking step
  if (_t_step < _tracking_step)
    return;

  // Reset Status on active unique grains
  std::vector<unsigned int> map_sizes(_maps_size);
  for (std::map<unsigned int, UniqueGrain *>::iterator grain_it = _unique_grains.begin();
       grain_it != _unique_grains.end(); ++grain_it)
  {
    if (grain_it->second->status != INACTIVE)
    {
      grain_it->second->status = NOT_MARKED;
      map_sizes[grain_it->second->variable_idx]++;
    }
  }

  // Print out info on the number of unique grains per variable vs the incoming bubble set sizes
  if (_t_step > _tracking_step)
  {
    for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    {
      Moose::out << "\nGrains active index " << map_num << ": " << map_sizes[map_num] << " -> " << _bubble_sets[map_num].size();
      if (map_sizes[map_num] != _bubble_sets[map_num].size())
        Moose::out << "**";
    }
    Moose::out << std::endl;
  }

  std::vector<UniqueGrain *> new_grains; new_grains.reserve(_unique_grains.size());

  // Loop over all the current regions and build our unique grain structures
  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
  {
    for (std::list<BubbleData>::const_iterator it1 = _bubble_sets[map_num].begin();
         it1 != _bubble_sets[map_num].end(); ++it1)
    {
      std::vector<BoundingSphereInfo *> sphere_ptrs;
      unsigned int curr_var = it1->_var_idx;

      for (std::list<BoundingSphereInfo *>::iterator it2 = _bounding_spheres[map_num].begin();
           it2 != _bounding_spheres[map_num].end(); /* No increment here! */)
      {
        /**
         * See which of the bounding spheres belong to the current region (bubble set) by looking at a
         * member node id.  A single region may have multiple bounding spheres as members if it spans
         * periodic boundaries
         */
        if (it1->_entity_ids.find((*it2)->member_node_id) != it1->_entity_ids.end())
        {
          // Transfer ownership of the bounding sphere info to "sphere_ptrs" which will be stored in the unique grain
          sphere_ptrs.push_back(*it2);
          // Now delete the current BoundingSpherestructure so that it won't be inspected or reused
          _bounding_spheres[map_num].erase(it2++);
        }
        else
          ++it2;
      }

      // Create our new grains from this timestep that we will use to match up against the existing grains
      new_grains.push_back(new UniqueGrain(curr_var, sphere_ptrs, &it1->_entity_ids, NOT_MARKED));
    }
  }

  /**
   * If it's the first time through this routine for the simulation, we will generate the unique grain
   * numbers using a simple counter.  These will be the unique grain numbers that we must track for
   * the remainder of the simulation.
   */
  if (_t_step == _tracking_step)   // Start tracking when the time_step == the tracking_step
  {
    if (_ebsd_reader)
    {
      Real grain_num = _ebsd_reader->getGrainNum();

      std::vector<Point> center_points(grain_num);

      for (unsigned int gr=0; gr < grain_num; ++gr)
      {
        const EBSDReader::EBSDAvgData & d = _ebsd_reader->getAvgData(gr);
        center_points[gr] = d.p;

        Moose::out << "EBSD Grain " << gr << " " << center_points[gr] << '\n';
      }

      // To find the minimum distance we will use the boundingRegionDistance routine.
      // To do that, we need to build BoundingSphereObjects with a few dummy values, radius and node_id will be ignored
      BoundingSphereInfo ebsd_sphere(0, Point(0, 0, 0), 1);
      std::vector<BoundingSphereInfo *> ebsd_vector(1);
      ebsd_vector[0] = &ebsd_sphere;

      std::set<unsigned int> used_indices;
      std::map<unsigned int, unsigned int> error_indices;

      if (grain_num != new_grains.size())
        mooseWarning("Mismatch:\nEBSD centers: " << grain_num << " Grain Tracker Centers: " << new_grains.size());

      unsigned int next_index = grain_num+1;
      for (unsigned int i = 0; i < new_grains.size(); ++i)
      {
        Real min_centroid_diff = std::numeric_limits<Real>::max();
        unsigned int closest_match_idx = 0;

        //Point center_of_mass = centerOfMass(*new_grains[i]);

        for (unsigned int j = 0; j<center_points.size(); ++j)
        {
          // Update the ebsd sphere to be used in the boundingRegionDistance calculation
          ebsd_sphere.b_sphere.center() = center_points[j];

          Real curr_centroid_diff = boundingRegionDistance(ebsd_vector, new_grains[i]->sphere_ptrs, true);
          //Real curr_centroid_diff = (center_points[j] - center_of_mass).size();
          if (curr_centroid_diff <= min_centroid_diff)
          {
            closest_match_idx = j;
            min_centroid_diff = curr_centroid_diff;
          }
        }

        if (used_indices.find(closest_match_idx) != used_indices.end())
        {
          Moose::out << "Re-assigning center " << closest_match_idx << " -> " << next_index << " "
                     << center_points[closest_match_idx] << " absolute distance: " << min_centroid_diff << '\n';
          _unique_grains[next_index] = new_grains[i];

          _unique_grain_to_ebsd_num[next_index] = closest_match_idx+1;

          ++next_index;
        }
        else
        {
          Moose::out << "Assigning center " << closest_match_idx << " "
                     << center_points[closest_match_idx] << " absolute distance: " << min_centroid_diff << '\n';
          _unique_grains[closest_match_idx+1] = new_grains[i];

          _unique_grain_to_ebsd_num[closest_match_idx+1] = closest_match_idx+1;

          used_indices.insert(closest_match_idx);
        }
      }
      if (!error_indices.empty())
      {
        for (std::map<unsigned int, UniqueGrain *>::const_iterator grain_it = _unique_grains.begin(); grain_it != _unique_grains.end(); ++grain_it)
          Moose::out << "Grain " << grain_it->first << ": " << center_points[grain_it->first - 1] << '\n';

        Moose::out << "Error Indices:\n";
        for (std::map<unsigned int, unsigned int>::const_iterator it = error_indices.begin(); it != error_indices.end(); ++it)
          Moose::out << "Grain " << it->first << '(' << it->second << ')' << ": " << center_points[it->second] << '\n';

        mooseError("Error with ESBD Mapping (see above unused indices)");
      }

      print();
    }
    else
    {
      for (unsigned int i = 1; i <= new_grains.size(); ++i)
      {
        new_grains[i-1]->status = MARKED;
        _unique_grains[i] = new_grains[i-1];                   // Transfer ownership of the memory
      }
    }
    return;  // Return early - no matching or tracking to do
  }

  /**
   * To track grains across timesteps, we will loop over our unique grains and link each one up with one of our new
   * unique grains.  The criteria for doing this will be to find the unique grain in the new list with a matching variable
   * index whose centroid is closest to this unique grain.
   */
  std::map<unsigned int, std::vector<unsigned int> > new_grain_idx_to_existing_grain_idx;

  for (std::map<unsigned int, UniqueGrain *>::iterator curr_it = _unique_grains.begin(); curr_it != _unique_grains.end(); ++curr_it)
  {
    if (curr_it->second->status == INACTIVE)                         // Don't try to find matches for inactive grains
      continue;

    unsigned int closest_match_idx;
    // bool found_one = false;
    Real min_centroid_diff = std::numeric_limits<Real>::max();

    for (unsigned int new_grain_idx = 0; new_grain_idx<new_grains.size(); ++new_grain_idx)
    {
      if (curr_it->second->variable_idx == new_grains[new_grain_idx]->variable_idx)  // Do the variables indicies match?
      {
        Real curr_centroid_diff = boundingRegionDistance(curr_it->second->sphere_ptrs, new_grains[new_grain_idx]->sphere_ptrs, true);
        if (curr_centroid_diff <= min_centroid_diff)
        {
          // found_one = true;
          closest_match_idx = new_grain_idx;
          min_centroid_diff = curr_centroid_diff;
        }
      }
    }

    // Keep track of which new grains the existing ones want to map to
    new_grain_idx_to_existing_grain_idx[closest_match_idx].push_back(curr_it->first);
  }

  /**
   * It's possible that multiple existing grains will map to a single new grain.  This will happen any time a grain disappears during this timestep.
   * We need to figure out the rightful owner in this case and inactivate the old grain.
   */
  for (std::map<unsigned int, std::vector<unsigned int> >::iterator it = new_grain_idx_to_existing_grain_idx.begin();
       it != new_grain_idx_to_existing_grain_idx.end(); ++it)
  {
    // If there is only a single mapping - we've found the correct grain
    if (it->second.size() == 1)
    {
      unsigned int curr_idx = (it->second)[0];
      delete _unique_grains[curr_idx];                      // clean-up old grain
      new_grains[it->first]->status = MARKED;               // Mark it
      _unique_grains[curr_idx] = new_grains[it->first];     // transfer ownership of new grain
    }

    // More than one existing grain is mapping to a new one
    else
    {
      Real min_centroid_diff = std::numeric_limits<Real>::max();
      unsigned int min_idx = 0;
      for (unsigned int i = 0; i < it->second.size(); ++i)
      {
        Real curr_centroid_diff = boundingRegionDistance(new_grains[it->first]->sphere_ptrs, _unique_grains[(it->second)[i]]->sphere_ptrs, true);
        if (curr_centroid_diff <= min_centroid_diff)
        {
          min_idx = i;
          min_centroid_diff = curr_centroid_diff;
        }
      }

      // One more time over the competing indices.  We will mark the non-winners as inactive and transfer ownership to the winner (the closest centroid).
      for (unsigned int i = 0; i < it->second.size(); ++i)
      {
        unsigned int curr_idx = (it->second)[i];
        if (i == min_idx)
        {
          delete _unique_grains[curr_idx];                      // clean-up old grain
          new_grains[it->first]->status = MARKED;               // Mark it
          _unique_grains[curr_idx] = new_grains[it->first];     // transfer ownership of new grain
        }
        else
        {
          Moose::out << "Marking Grain " << curr_idx << " as INACTIVE (varible index: "
                    << _unique_grains[curr_idx]->variable_idx <<  ")\n";
          _unique_grains[curr_idx]->status = INACTIVE;
        }
      }
    }
  }


  /**
   * Next we need to look at our new list and see which grains weren't matched up.  These are new grains.
   */
  for (unsigned int i = 0; i < new_grains.size(); ++i)
    if (new_grains[i]->status == NOT_MARKED)
    {
      Moose::out << COLOR_YELLOW
                 << "*****************************************************************************\n"
                 << "Couldn't find a matching grain while working on variable index: " << new_grains[i]->variable_idx
                 << "\nCreating new unique grain: " << _unique_grains.size() + 1
                 << "\n*****************************************************************************\n" << COLOR_DEFAULT;
      new_grains[i]->status = MARKED;
      _unique_grains[_unique_grains.size() + 1] = new_grains[i];   // transfer ownership
    }


  /**
   * Finally we need to mark any grains in the unique list that aren't marked as inactive.  These are the variables that
   * unique grains that didn't match up to any bounding sphere.  Should only happen if it's the last active grain for
   * this particular variable.
   */
  for (std::map<unsigned int, UniqueGrain *>::iterator it = _unique_grains.begin(); it != _unique_grains.end(); ++it)
    if (it->second->status == NOT_MARKED)
    {
      Moose::out << "Marking Grain " << it->first << " as INACTIVE (varible index: "
                    << it->second->variable_idx <<  ")\n";
      it->second->status = INACTIVE;
    }


  // Sanity check to make sure that we consumed all of the bounding sphere datastructures
  for (unsigned int map_num = 0; map_num < _maps_size; ++map_num)
    if (!_bounding_spheres[map_num].empty())
      mooseError("BoundingSpheres where not completely used by the GrainTracker");
}