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; } }
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"); }