Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "EBSDReader.h"
11 : #include "EBSDMesh.h"
12 : #include "MooseMesh.h"
13 : #include "Conversion.h"
14 : #include "NonlinearSystem.h"
15 :
16 : #include "libmesh/int_range.h"
17 :
18 : #include <Eigen/Dense>
19 :
20 : #include <fstream>
21 :
22 : registerMooseObject("PhaseFieldApp", EBSDReader);
23 :
24 : InputParameters
25 336 : EBSDReader::validParams()
26 : {
27 336 : InputParameters params = EulerAngleProvider::validParams();
28 336 : params.addClassDescription("Load and manage DREAM.3D EBSD data files for running simulations on "
29 : "reconstructed microstructures.");
30 672 : params.addParam<unsigned int>(
31 672 : "custom_columns", 0, "Number of additional custom data columns to read from the EBSD file");
32 672 : params.addParam<unsigned int>("bins", 20, "Number of bins to segregate quaternions");
33 672 : params.addParam<Real>("L_norm", 1, "Specifies the type of average the user intends to perform");
34 672 : params.addParam<std::string>("ebsd_meshgenerator",
35 : "Specify the name of the EBSDMeshGenerator. The EBSDReader can "
36 : "autodetect this, if only one such MeshGenerator exists.");
37 336 : return params;
38 0 : }
39 :
40 168 : EBSDReader::EBSDReader(const InputParameters & params)
41 : : EulerAngleProvider(params),
42 336 : _mesh(_fe_problem.mesh()),
43 168 : _nl(_fe_problem.getNonlinearSystemBase(_sys.number())),
44 168 : _grain_num(0),
45 336 : _custom_columns(getParam<unsigned int>("custom_columns")),
46 168 : _time_step(_fe_problem.timeStep()),
47 168 : _mesh_dimension(_mesh.dimension()),
48 336 : _bins(getParam<unsigned int>("bins")),
49 336 : _L_norm(getParam<Real>("L_norm")),
50 168 : _nx(0),
51 168 : _ny(0),
52 168 : _nz(0),
53 168 : _dx(0.),
54 168 : _dy(0.),
55 168 : _dz(0.)
56 : {
57 168 : readFile();
58 : // throws an error for zero bins
59 168 : if (_bins == 0)
60 0 : mooseError("One cannot have zero bins");
61 168 : }
62 :
63 : void
64 168 : EBSDReader::readFile()
65 : {
66 : std::string ebsd_filename;
67 : EBSDMeshGenerator::Geometry geometry;
68 :
69 : // Fetch and check mesh or meshgenerators
70 168 : EBSDMesh * mesh = dynamic_cast<EBSDMesh *>(&_mesh);
71 168 : if (mesh != NULL)
72 : {
73 : ebsd_filename = mesh->getEBSDFilename();
74 0 : geometry = mesh->getEBSDGeometry();
75 : }
76 : else
77 : {
78 : std::string ebsd_meshgenerator_name;
79 336 : if (isParamValid("ebsd_meshgenerator"))
80 0 : ebsd_meshgenerator_name = getParam<std::string>("ebsd_meshgenerator");
81 : else
82 : {
83 168 : auto meshgenerator_names = _app.getMeshGeneratorNames();
84 504 : for (auto & mgn : meshgenerator_names)
85 : {
86 : const EBSDMeshGenerator * emg =
87 336 : dynamic_cast<const EBSDMeshGenerator *>(&_app.getMeshGenerator(mgn));
88 336 : if (emg)
89 : {
90 168 : if (!ebsd_meshgenerator_name.empty())
91 0 : mooseError("Found multiple EBSDMeshGenerator objects (",
92 : ebsd_meshgenerator_name,
93 : " and ",
94 : mgn,
95 : "). Use the 'ebsd_meshgenerator' parameter to specify which one to use.");
96 : ebsd_meshgenerator_name = mgn;
97 : }
98 : }
99 :
100 168 : if (ebsd_meshgenerator_name.empty())
101 0 : mooseError("Failed to autodetect an EBSDMeshGenerator (or a deprecated EBSDMesh object).");
102 168 : }
103 :
104 : // get the selected or detected mesh generator
105 : const EBSDMeshGenerator * emg =
106 168 : dynamic_cast<const EBSDMeshGenerator *>(&_app.getMeshGenerator(ebsd_meshgenerator_name));
107 168 : if (!emg)
108 0 : paramError("ebsd_meshgenerator", "No valid EBSDMeshGenerator object found.");
109 :
110 : ebsd_filename = emg->getEBSDFilename();
111 168 : geometry = emg->getEBSDGeometry();
112 : }
113 :
114 168 : std::ifstream stream_in(ebsd_filename.c_str());
115 168 : if (!stream_in)
116 0 : mooseError("Can't open EBSD file: ", ebsd_filename);
117 :
118 : // Copy file header data from the EBSDMesh
119 168 : _dx = geometry.d[0];
120 168 : _nx = geometry.n[0];
121 168 : _minx = geometry.min[0];
122 168 : _maxx = _minx + _dx * _nx;
123 :
124 168 : _dy = geometry.d[1];
125 168 : _ny = geometry.n[1];
126 168 : _miny = geometry.min[1];
127 168 : _maxy = _miny + _dy * _ny;
128 :
129 168 : _dz = geometry.d[2];
130 168 : _nz = geometry.n[2];
131 168 : _minz = geometry.min[2];
132 168 : _maxz = _minz + _dz * _nz;
133 :
134 : // Resize the _data array
135 168 : unsigned total_size = geometry.dim < 3 ? _nx * _ny : _nx * _ny * _nz;
136 168 : _data.resize(total_size);
137 :
138 : std::string line;
139 299320 : while (std::getline(stream_in, line))
140 : {
141 299152 : if (line.find("#") != 0)
142 : {
143 : // Temporary variables to read in on each line
144 : EBSDPointData d;
145 : Real x, y, z;
146 :
147 293306 : std::istringstream iss(line);
148 : iss >> d._phi1 >> d._Phi >> d._phi2 >> x >> y >> z >> d._feature_id >> d._phase >>
149 : d._symmetry;
150 :
151 : // Transform angles to degrees
152 293306 : d._phi1 *= 180.0 / libMesh::pi;
153 293306 : d._Phi *= 180.0 / libMesh::pi;
154 293306 : d._phi2 *= 180.0 / libMesh::pi;
155 :
156 : // Custom columns
157 293306 : d._custom.resize(_custom_columns);
158 293306 : for (unsigned int i = 0; i < _custom_columns; ++i)
159 0 : if (!(iss >> d._custom[i]))
160 0 : mooseError("Unable to read in EBSD custom data column #", i);
161 :
162 293306 : if (x < _minx || y < _miny || x > _maxx || y > _maxy ||
163 0 : (geometry.dim == 3 && (z < _minz || z > _maxz)))
164 0 : mooseError("EBSD Data ouside of the domain declared in the header ([",
165 0 : _minx,
166 0 : ':',
167 0 : _maxx,
168 : "], [",
169 0 : _miny,
170 0 : ':',
171 0 : _maxy,
172 : "], [",
173 0 : _minz,
174 0 : ':',
175 0 : _maxz,
176 : "]) dim=",
177 : geometry.dim,
178 : "\n",
179 : line);
180 :
181 293306 : d._p = Point(x, y, z);
182 :
183 : // determine number of grains in the dataset
184 293306 : if (_global_id_map.find(d._feature_id) == _global_id_map.end())
185 3546 : _global_id_map[d._feature_id] = _grain_num++;
186 :
187 293306 : unsigned int global_index = indexFromPoint(Point(x, y, z));
188 293306 : _data[global_index] = d;
189 293306 : }
190 : }
191 168 : stream_in.close();
192 :
193 : // Resize the variables
194 168 : _avg_data.resize(_grain_num);
195 168 : _avg_angles.resize(_grain_num);
196 :
197 : // clear the averages
198 3714 : for (const auto i : make_range(_grain_num))
199 : {
200 3546 : EBSDAvgData & a = _avg_data[i];
201 3546 : a._symmetry = a._phase = a._n = 0;
202 3546 : a._p = 0.0;
203 3546 : a._custom.assign(_custom_columns, 0.0);
204 :
205 : EulerAngles & b = _avg_angles[i];
206 3546 : b.phi1 = b.Phi = b.phi2 = 0.0;
207 : }
208 :
209 : // Array of vectors to store quaternions of each grain
210 168 : std::vector<std::vector<Eigen::Quaternion<Real>>> quat(_grain_num);
211 :
212 : // Iterate through data points to store orientation information for each grain
213 293474 : for (auto & j : _data)
214 : {
215 293306 : EBSDAvgData & a = _avg_data[_global_id_map[j._feature_id]];
216 293306 : EulerAngles angles;
217 :
218 293306 : angles.phi1 = j._phi1;
219 293306 : angles.Phi = j._Phi;
220 293306 : angles.phi2 = j._phi2;
221 :
222 : // convert Euler angles to quaternions
223 293306 : Eigen::Quaternion<Real> q = angles.toQuaternion();
224 293306 : quat[_global_id_map[j._feature_id]].push_back(q);
225 :
226 293306 : if (a._n == 0)
227 3546 : a._phase = j._phase;
228 289760 : else if (a._phase != j._phase)
229 0 : mooseError("An EBSD feature needs to have a uniform phase.");
230 :
231 293306 : if (a._n == 0)
232 3546 : a._symmetry = j._symmetry;
233 289760 : else if (a._symmetry != j._symmetry)
234 0 : mooseError("An EBSD feature needs to have a uniform symmetry parameter.");
235 :
236 293306 : for (unsigned int i = 0; i < _custom_columns; ++i)
237 0 : a._custom[i] += j._custom[i];
238 :
239 : // store the feature (or grain) ID
240 293306 : a._feature_id = j._feature_id;
241 :
242 : a._p += j._p;
243 293306 : a._n++;
244 : }
245 :
246 3714 : for (const auto i : make_range(_grain_num))
247 : {
248 3546 : EBSDAvgData & a = _avg_data[i];
249 : EulerAngles & b = _avg_angles[i];
250 :
251 3546 : if (a._n == 0)
252 0 : continue;
253 :
254 : // creating a map to store the quaternion count for each bin index
255 : std::map<std::tuple<int, int, int, int>, unsigned int> feature_weights;
256 :
257 : // looping through all quaternions of the current grain
258 296852 : for (const auto & q : quat[i])
259 : {
260 293306 : const auto bin = std::make_tuple<int, int, int, int>(std::floor(q.w() * 0.5 * _bins),
261 293306 : std::floor(q.x() * 0.5 * _bins),
262 293306 : std::floor(q.y() * 0.5 * _bins),
263 293306 : std::floor(q.z() * 0.5 * _bins));
264 293306 : feature_weights[bin]++;
265 : }
266 :
267 : /**
268 : * Markley, F. Landis, Yang Cheng, John Lucas Crassidis, and Yaakov Oshman.
269 : * "Averaging quaternions." Journal of Guidance, Control, and Dynamics 30,
270 : * no. 4 (2007): 1193-1197.
271 : * A 4 by N matrix (Q) is constructed, where N is the number of quaternions.
272 : * A weight matrix (W) is created. The eigenvector corresponding to the
273 : * maximum eigenvalue of Q*W*Q' is the weighted average quaternion
274 : */
275 :
276 : // quaternion average matrix Q*w*Q^T
277 : typedef Eigen::Matrix<Real, 4, 4> Matrix4x4;
278 : Matrix4x4 quat_mat = Matrix4x4::Zero();
279 : typedef Eigen::Matrix<Real, 4, 1> Vector4;
280 :
281 : bool data_quality_ok = false;
282 : Real total_weight = 0.0;
283 296852 : for (const auto & q : quat[i])
284 : {
285 : Vector4 v(q.w(), q.x(), q.y(), q.z());
286 :
287 293306 : const auto bin = std::make_tuple<int, int, int, int>(std::floor(q.w() * 0.5 * _bins),
288 293306 : std::floor(q.x() * 0.5 * _bins),
289 293306 : std::floor(q.y() * 0.5 * _bins),
290 293306 : std::floor(q.z() * 0.5 * _bins));
291 293306 : const auto bin_size = feature_weights[bin];
292 293306 : const auto weight = std::pow(bin_size, _L_norm);
293 293306 : total_weight += weight;
294 :
295 293306 : quat_mat += v * weight * v.transpose();
296 :
297 : /**
298 : * If no bin exists which has at least 50% of total quaternions in a grain
299 : * then the EBSD data may not be reliable
300 : * Note: The limit 50% is arbitrary
301 : */
302 293306 : if (bin_size * 2 > quat[i].size())
303 : data_quality_ok = true;
304 : }
305 :
306 3546 : quat_mat *= 1.0 / total_weight;
307 :
308 : // throws a warning if EBSD data is not reliable
309 3546 : if (!data_quality_ok)
310 35 : _console << COLOR_YELLOW << "EBSD orientation data may not be reliable for grain " << i
311 28 : << '\n'
312 35 : << COLOR_DEFAULT << std::flush;
313 :
314 : // compute eigenvalues and eigenvectors
315 3546 : Eigen::EigenSolver<Matrix4x4> EigenSolver(quat_mat);
316 3546 : Vector4 eigen_values = EigenSolver.eigenvalues().real();
317 3546 : Matrix4x4 eigen_vectors = EigenSolver.eigenvectors().real();
318 :
319 : // Selecting eigenvector corresponding to max eigenvalue to compute average Euler angle
320 3546 : Vector4::Index max_index = 0;
321 : eigen_values.maxCoeff(&max_index);
322 3546 : const auto max_vec = eigen_vectors.col(max_index);
323 : const Eigen::Quaternion<Real> q(max_vec(0), max_vec(1), max_vec(2), max_vec(3));
324 3546 : b = EulerAngles(q);
325 :
326 : // link the EulerAngles into the EBSDAvgData for access via the functors
327 3546 : a._angles = &b;
328 :
329 3546 : if (a._phase >= _global_id.size())
330 194 : _global_id.resize(a._phase + 1);
331 :
332 : // The averaged per grain data locally contains the phase id, local id, and
333 : // original feature id. It is stored contiguously indexed by global id.
334 3546 : a._local_id = _global_id[a._phase].size();
335 3546 : _global_id[a._phase].push_back(i);
336 :
337 3546 : a._p *= 1.0 / Real(a._n);
338 :
339 3546 : for (unsigned int i = 0; i < _custom_columns; ++i)
340 0 : a._custom[i] /= Real(a._n);
341 : }
342 : // Build maps to indicate the weights with which grain and phase data
343 : // from the surrounding elements contributes to a node for IC purposes
344 168 : buildNodeWeightMaps();
345 504 : }
346 :
347 672 : EBSDReader::~EBSDReader() {}
348 :
349 : const EBSDReader::EBSDPointData &
350 1400456 : EBSDReader::getData(const Point & p) const
351 : {
352 1400456 : return _data[indexFromPoint(p)];
353 : }
354 :
355 : const EBSDReader::EBSDAvgData &
356 242848 : EBSDReader::getAvgData(unsigned int var) const
357 : {
358 242848 : return _avg_data[indexFromIndex(var)];
359 : }
360 :
361 : const EulerAngles &
362 1054190 : EBSDReader::getEulerAngles(unsigned int var) const
363 : {
364 1054190 : return _avg_angles[indexFromIndex(var)];
365 : }
366 :
367 : const EBSDReader::EBSDAvgData &
368 32539 : EBSDReader::getAvgData(unsigned int phase, unsigned int local_id) const
369 : {
370 32539 : return _avg_data[indexFromIndex(_global_id[phase][local_id])];
371 : }
372 :
373 : unsigned int
374 2056076 : EBSDReader::getGrainNum() const
375 : {
376 2056076 : return _grain_num;
377 : }
378 :
379 : unsigned int
380 840744 : EBSDReader::getGrainNum(unsigned int phase) const
381 : {
382 840744 : return _global_id[phase].size();
383 : }
384 :
385 : unsigned int
386 1693762 : EBSDReader::indexFromPoint(const Point & p) const
387 : {
388 : // Don't assume an ordering on the input data, use the (x, y,
389 : // z) values of this centroid to determine the index.
390 : unsigned int x_index, y_index, z_index, global_index;
391 :
392 1693762 : x_index = (unsigned int)((p(0) - _minx) / _dx);
393 1693762 : y_index = (unsigned int)((p(1) - _miny) / _dy);
394 1693762 : if (p(0) <= _minx || p(0) >= _maxx || p(1) <= _miny || p(1) >= _maxy)
395 0 : mooseError("Data points must be on the interior of the mesh elements. In EBSDReader ", name());
396 :
397 1693762 : if (_mesh_dimension == 3)
398 : {
399 0 : z_index = (unsigned int)((p(2) - _minz) / _dz);
400 0 : global_index = z_index * _ny;
401 0 : if (p(2) <= _minz || p(2) >= _maxz)
402 0 : mooseError("Data points must be on the interior of the mesh elements. In EBSDReader ",
403 : name());
404 : }
405 : else
406 : global_index = 0;
407 :
408 : // Compute the global index into the _data array. This stores points
409 : // in a [z][y][x] ordering.
410 1693762 : global_index = (global_index + y_index) * _nx + x_index;
411 :
412 : // Don't access out of range!
413 : mooseAssert(global_index < _data.size(),
414 : "global_index " << global_index << " points out of _data range: " << _data.size());
415 :
416 1693762 : return global_index;
417 : }
418 :
419 : unsigned int
420 1329577 : EBSDReader::indexFromIndex(unsigned int var) const
421 : {
422 :
423 : // Transfer the index into the _avg_data array.
424 1329577 : unsigned avg_index = var;
425 :
426 : // Don't access out of range!
427 1329577 : if (avg_index >= _avg_data.size())
428 0 : mooseError("Error! Index out of range in EBSDReader::indexFromIndex(), index: ",
429 : avg_index,
430 : " size: ",
431 0 : _avg_data.size());
432 :
433 1329577 : return avg_index;
434 : }
435 :
436 : const std::map<dof_id_type, std::vector<Real>> &
437 152 : EBSDReader::getNodeToGrainWeightMap() const
438 : {
439 152 : return _node_to_grain_weight_map;
440 : }
441 :
442 : const std::map<dof_id_type, std::vector<Real>> &
443 24 : EBSDReader::getNodeToPhaseWeightMap() const
444 : {
445 24 : return _node_to_phase_weight_map;
446 : }
447 :
448 : unsigned int
449 1159532 : EBSDReader::getGlobalID(unsigned int feature_id) const
450 : {
451 : auto it = _global_id_map.find(feature_id);
452 1159532 : if (it == _global_id_map.end())
453 0 : mooseError("Invalid Feature ID");
454 1159532 : return it->second;
455 : }
456 :
457 : void
458 16 : EBSDReader::meshChanged()
459 : {
460 : // maps are only rebuild for use in initial conditions, which happens in time step zero
461 16 : if (_time_step == 0)
462 16 : buildNodeWeightMaps();
463 16 : }
464 :
465 : void
466 184 : EBSDReader::buildNodeWeightMaps()
467 : {
468 : // Import nodeToElemMap from MooseMesh for current node
469 : // This map consists of the node index followed by a vector of element indices that are associated
470 : // with that node
471 : const std::map<dof_id_type, std::vector<dof_id_type>> & node_to_elem_map =
472 184 : _mesh.nodeToActiveSemilocalElemMap();
473 184 : libMesh::MeshBase & mesh = _mesh.getMesh();
474 :
475 : // Loop through each node in mesh and calculate eta values for each grain associated with the node
476 638814 : for (const auto & node : as_range(mesh.active_nodes_begin(), mesh.active_nodes_end()))
477 : {
478 : // Get node_id
479 319131 : const dof_id_type node_id = node->id();
480 :
481 : // Initialize map entries for current node
482 319131 : _node_to_grain_weight_map[node_id].assign(getGrainNum(), 0.0);
483 319131 : _node_to_phase_weight_map[node_id].assign(getPhaseNum(), 0.0);
484 :
485 : // Loop through element indices associated with the current node and record weighted eta value
486 : // in new map
487 : const auto & node_to_elem_pair = node_to_elem_map.find(node_id);
488 319131 : if (node_to_elem_pair != node_to_elem_map.end())
489 : {
490 : unsigned int n_elems =
491 : node_to_elem_pair->second
492 245634 : .size(); // n_elems can range from 1 to 4 for 2D and 1 to 8 for 3D problems
493 :
494 1179114 : for (unsigned int ne = 0; ne < n_elems; ++ne)
495 : {
496 : // Current element index
497 933480 : unsigned int elem_id = (node_to_elem_pair->second)[ne];
498 :
499 : // Retrieve EBSD grain number for the current element index
500 933480 : const Elem * elem = mesh.elem_ptr(elem_id);
501 933480 : const EBSDReader::EBSDPointData & d = getData(elem->vertex_average());
502 :
503 : // get the (global) grain ID for the EBSD feature ID
504 933480 : const unsigned int global_id = getGlobalID(d._feature_id);
505 :
506 : // Calculate eta value and add to map
507 933480 : _node_to_grain_weight_map[node_id][global_id] += 1.0 / n_elems;
508 933480 : _node_to_phase_weight_map[node_id][d._phase] += 1.0 / n_elems;
509 : }
510 : }
511 184 : }
512 184 : }
513 :
514 : MooseSharedPointer<EBSDAccessFunctors::EBSDPointDataFunctor>
515 344 : EBSDReader::getPointDataAccessFunctor(const MooseEnum & field_name) const
516 : {
517 : EBSDPointDataFunctor * ret_val = NULL;
518 :
519 344 : switch (field_name)
520 : {
521 66 : case 0: // phi1
522 66 : ret_val = new EBSDPointDataPhi1();
523 : break;
524 66 : case 1: // phi
525 66 : ret_val = new EBSDPointDataPhi();
526 : break;
527 66 : case 2: // phi2
528 66 : ret_val = new EBSDPointDataPhi2();
529 : break;
530 116 : case 3: // grain
531 116 : ret_val = new EBSDPointDataFeatureID();
532 : break;
533 18 : case 4: // phase
534 18 : ret_val = new EBSDPointDataPhase();
535 : break;
536 12 : case 5: // symmetry
537 12 : ret_val = new EBSDPointDataSymmetry();
538 : break;
539 0 : default:
540 : {
541 : // check for custom columns
542 0 : for (const auto i : make_range(_custom_columns))
543 0 : if (field_name == "CUSTOM" + Moose::stringify(i))
544 : {
545 0 : ret_val = new EBSDPointDataCustom(i);
546 : break;
547 : }
548 : }
549 : }
550 :
551 : // If ret_val was not set by any of the above cases, throw an error.
552 : if (!ret_val)
553 0 : mooseError("Error: Please input supported EBSD_param");
554 :
555 : // If we made it here, wrap up the the ret_val in a
556 : // MooseSharedPointer and ship it off.
557 344 : return MooseSharedPointer<EBSDPointDataFunctor>(ret_val);
558 : }
559 :
560 : MooseSharedPointer<EBSDAccessFunctors::EBSDAvgDataFunctor>
561 36 : EBSDReader::getAvgDataAccessFunctor(const MooseEnum & field_name) const
562 : {
563 : EBSDAvgDataFunctor * ret_val = NULL;
564 :
565 36 : switch (field_name)
566 : {
567 0 : case 0: // phi1
568 0 : ret_val = new EBSDAvgDataPhi1();
569 : break;
570 0 : case 1: // phi
571 0 : ret_val = new EBSDAvgDataPhi();
572 : break;
573 0 : case 2: // phi2
574 0 : ret_val = new EBSDAvgDataPhi2();
575 : break;
576 0 : case 3: // phase
577 0 : ret_val = new EBSDAvgDataPhase();
578 : break;
579 0 : case 4: // symmetry
580 0 : ret_val = new EBSDAvgDataSymmetry();
581 : break;
582 0 : case 5: // local_id
583 0 : ret_val = new EBSDAvgDataLocalID();
584 : break;
585 36 : case 6: // feature_id
586 36 : ret_val = new EBSDAvgDataFeatureID();
587 : break;
588 0 : default:
589 : {
590 : // check for custom columns
591 0 : for (const auto i : make_range(_custom_columns))
592 0 : if (field_name == "CUSTOM" + Moose::stringify(i))
593 : {
594 0 : ret_val = new EBSDAvgDataCustom(i);
595 : break;
596 : }
597 : }
598 : }
599 :
600 : // If ret_val was not set by any of the above cases, throw an error.
601 : if (!ret_val)
602 0 : mooseError("Error: Please input supported EBSD_param");
603 :
604 : // If we made it here, wrap up the the ret_val in a
605 : // MooseSharedPointer and ship it off.
606 36 : return MooseSharedPointer<EBSDAvgDataFunctor>(ret_val);
607 : }
|