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 250 : EBSDReader::validParams()
26 : {
27 250 : InputParameters params = EulerAngleProvider::validParams();
28 250 : params.addClassDescription("Load and manage DREAM.3D EBSD data files for running simulations on "
29 : "reconstructed microstructures.");
30 500 : params.addParam<unsigned int>(
31 500 : "custom_columns", 0, "Number of additional custom data columns to read from the EBSD file");
32 500 : params.addParam<unsigned int>("bins", 20, "Number of bins to segregate quaternions");
33 500 : params.addParam<Real>("L_norm", 1, "Specifies the type of average the user intends to perform");
34 500 : 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 250 : return params;
38 0 : }
39 :
40 125 : EBSDReader::EBSDReader(const InputParameters & params)
41 : : EulerAngleProvider(params),
42 250 : _mesh(_fe_problem.mesh()),
43 125 : _nl(_fe_problem.getNonlinearSystemBase(_sys.number())),
44 125 : _grain_num(0),
45 250 : _custom_columns(getParam<unsigned int>("custom_columns")),
46 125 : _time_step(_fe_problem.timeStep()),
47 125 : _mesh_dimension(_mesh.dimension()),
48 250 : _bins(getParam<unsigned int>("bins")),
49 250 : _L_norm(getParam<Real>("L_norm")),
50 125 : _nx(0),
51 125 : _ny(0),
52 125 : _nz(0),
53 125 : _dx(0.),
54 125 : _dy(0.),
55 125 : _dz(0.)
56 : {
57 125 : readFile();
58 : // throws an error for zero bins
59 125 : if (_bins == 0)
60 0 : mooseError("One cannot have zero bins");
61 125 : }
62 :
63 : void
64 125 : EBSDReader::readFile()
65 : {
66 : std::string ebsd_filename;
67 : EBSDMeshGenerator::Geometry geometry;
68 :
69 : // Fetch and check mesh or meshgenerators
70 125 : EBSDMesh * mesh = dynamic_cast<EBSDMesh *>(&_mesh);
71 125 : 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 250 : if (isParamValid("ebsd_meshgenerator"))
80 0 : ebsd_meshgenerator_name = getParam<std::string>("ebsd_meshgenerator");
81 : else
82 : {
83 125 : auto meshgenerator_names = _app.getMeshGeneratorNames();
84 375 : for (auto & mgn : meshgenerator_names)
85 : {
86 : const EBSDMeshGenerator * emg =
87 250 : dynamic_cast<const EBSDMeshGenerator *>(&_app.getMeshGenerator(mgn));
88 250 : if (emg)
89 : {
90 125 : 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 125 : if (ebsd_meshgenerator_name.empty())
101 0 : mooseError("Failed to autodetect an EBSDMeshGenerator (or a deprecated EBSDMesh object).");
102 125 : }
103 :
104 : // get the selected or detected mesh generator
105 : const EBSDMeshGenerator * emg =
106 125 : dynamic_cast<const EBSDMeshGenerator *>(&_app.getMeshGenerator(ebsd_meshgenerator_name));
107 125 : if (!emg)
108 0 : paramError("ebsd_meshgenerator", "No valid EBSDMeshGenerator object found.");
109 :
110 : ebsd_filename = emg->getEBSDFilename();
111 125 : geometry = emg->getEBSDGeometry();
112 : }
113 :
114 125 : std::ifstream stream_in(ebsd_filename.c_str());
115 125 : if (!stream_in)
116 0 : mooseError("Can't open EBSD file: ", ebsd_filename);
117 :
118 : // Copy file header data from the EBSDMesh
119 125 : _dx = geometry.d[0];
120 125 : _nx = geometry.n[0];
121 125 : _minx = geometry.min[0];
122 125 : _maxx = _minx + _dx * _nx;
123 :
124 125 : _dy = geometry.d[1];
125 125 : _ny = geometry.n[1];
126 125 : _miny = geometry.min[1];
127 125 : _maxy = _miny + _dy * _ny;
128 :
129 125 : _dz = geometry.d[2];
130 125 : _nz = geometry.n[2];
131 125 : _minz = geometry.min[2];
132 125 : _maxz = _minz + _dz * _nz;
133 :
134 : // Resize the _data array
135 125 : unsigned total_size = geometry.dim < 3 ? _nx * _ny : _nx * _ny * _nz;
136 125 : _data.resize(total_size);
137 :
138 : std::string line;
139 221969 : while (std::getline(stream_in, line))
140 : {
141 221844 : if (line.find("#") != 0)
142 : {
143 : // Temporary variables to read in on each line
144 : EBSDPointData d;
145 : Real x, y, z;
146 :
147 217506 : 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 217506 : d._phi1 *= 180.0 / libMesh::pi;
153 217506 : d._Phi *= 180.0 / libMesh::pi;
154 217506 : d._phi2 *= 180.0 / libMesh::pi;
155 :
156 : // Custom columns
157 217506 : d._custom.resize(_custom_columns);
158 217506 : 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 217506 : 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 217506 : d._p = Point(x, y, z);
182 :
183 : // determine number of grains in the dataset
184 217506 : if (_global_id_map.find(d._feature_id) == _global_id_map.end())
185 2533 : _global_id_map[d._feature_id] = _grain_num++;
186 :
187 217506 : unsigned int global_index = indexFromPoint(Point(x, y, z));
188 217506 : _data[global_index] = d;
189 217506 : }
190 : }
191 125 : stream_in.close();
192 :
193 : // Resize the variables
194 125 : _avg_data.resize(_grain_num);
195 125 : _avg_angles.resize(_grain_num);
196 :
197 : // clear the averages
198 2658 : for (const auto i : make_range(_grain_num))
199 : {
200 2533 : EBSDAvgData & a = _avg_data[i];
201 2533 : a._symmetry = a._phase = a._n = 0;
202 2533 : a._p = 0.0;
203 2533 : a._custom.assign(_custom_columns, 0.0);
204 :
205 : EulerAngles & b = _avg_angles[i];
206 2533 : b.phi1 = b.Phi = b.phi2 = 0.0;
207 : }
208 :
209 : // Array of vectors to store quaternions of each grain
210 125 : std::vector<std::vector<Eigen::Quaternion<Real>>> quat(_grain_num);
211 :
212 : // Iterate through data points to store orientation information for each grain
213 217631 : for (auto & j : _data)
214 : {
215 217506 : EBSDAvgData & a = _avg_data[_global_id_map[j._feature_id]];
216 217506 : EulerAngles angles;
217 :
218 217506 : angles.phi1 = j._phi1;
219 217506 : angles.Phi = j._Phi;
220 217506 : angles.phi2 = j._phi2;
221 :
222 : // convert Euler angles to quaternions
223 217506 : Eigen::Quaternion<Real> q = angles.toQuaternion();
224 217506 : quat[_global_id_map[j._feature_id]].push_back(q);
225 :
226 217506 : if (a._n == 0)
227 2533 : a._phase = j._phase;
228 214973 : else if (a._phase != j._phase)
229 0 : mooseError("An EBSD feature needs to have a uniform phase.");
230 :
231 217506 : if (a._n == 0)
232 2533 : a._symmetry = j._symmetry;
233 214973 : else if (a._symmetry != j._symmetry)
234 0 : mooseError("An EBSD feature needs to have a uniform symmetry parameter.");
235 :
236 217506 : 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 217506 : a._feature_id = j._feature_id;
241 :
242 : a._p += j._p;
243 217506 : a._n++;
244 : }
245 :
246 2658 : for (const auto i : make_range(_grain_num))
247 : {
248 2533 : EBSDAvgData & a = _avg_data[i];
249 : EulerAngles & b = _avg_angles[i];
250 :
251 2533 : 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 220039 : for (const auto & q : quat[i])
259 : {
260 217506 : const auto bin = std::make_tuple<int, int, int, int>(std::floor(q.w() * 0.5 * _bins),
261 217506 : std::floor(q.x() * 0.5 * _bins),
262 217506 : std::floor(q.y() * 0.5 * _bins),
263 217506 : std::floor(q.z() * 0.5 * _bins));
264 217506 : 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 220039 : for (const auto & q : quat[i])
284 : {
285 : Vector4 v(q.w(), q.x(), q.y(), q.z());
286 :
287 217506 : const auto bin = std::make_tuple<int, int, int, int>(std::floor(q.w() * 0.5 * _bins),
288 217506 : std::floor(q.x() * 0.5 * _bins),
289 217506 : std::floor(q.y() * 0.5 * _bins),
290 217506 : std::floor(q.z() * 0.5 * _bins));
291 217506 : const auto bin_size = feature_weights[bin];
292 217506 : const auto weight = std::pow(bin_size, _L_norm);
293 217506 : total_weight += weight;
294 :
295 217506 : 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 217506 : if (bin_size * 2 > quat[i].size())
303 : data_quality_ok = true;
304 : }
305 :
306 2533 : quat_mat *= 1.0 / total_weight;
307 :
308 : // throws a warning if EBSD data is not reliable
309 2533 : if (!data_quality_ok)
310 28 : _console << COLOR_YELLOW << "EBSD orientation data may not be reliable for grain " << i
311 28 : << '\n'
312 28 : << COLOR_DEFAULT << std::flush;
313 :
314 : // compute eigenvalues and eigenvectors
315 2533 : Eigen::EigenSolver<Matrix4x4> EigenSolver(quat_mat);
316 2533 : Vector4 eigen_values = EigenSolver.eigenvalues().real();
317 2533 : Matrix4x4 eigen_vectors = EigenSolver.eigenvectors().real();
318 :
319 : // Selecting eigenvector corresponding to max eigenvalue to compute average Euler angle
320 2533 : Vector4::Index max_index = 0;
321 : eigen_values.maxCoeff(&max_index);
322 2533 : 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 2533 : b = EulerAngles(q);
325 :
326 : // link the EulerAngles into the EBSDAvgData for access via the functors
327 2533 : a._angles = &b;
328 :
329 2533 : if (a._phase >= _global_id.size())
330 147 : _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 2533 : a._local_id = _global_id[a._phase].size();
335 2533 : _global_id[a._phase].push_back(i);
336 :
337 2533 : a._p *= 1.0 / Real(a._n);
338 :
339 2533 : 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 125 : buildNodeWeightMaps();
345 375 : }
346 :
347 375 : EBSDReader::~EBSDReader() {}
348 :
349 : const EBSDReader::EBSDPointData &
350 1175181 : EBSDReader::getData(const Point & p) const
351 : {
352 1175181 : return _data[indexFromPoint(p)];
353 : }
354 :
355 : const EBSDReader::EBSDAvgData &
356 211500 : EBSDReader::getAvgData(unsigned int var) const
357 : {
358 211500 : return _avg_data[indexFromIndex(var)];
359 : }
360 :
361 : const EulerAngles &
362 3064098 : EBSDReader::getEulerAngles(unsigned int var) const
363 : {
364 3064098 : return _avg_angles[indexFromIndex(var)];
365 : }
366 :
367 : const EBSDReader::EBSDAvgData &
368 27533 : EBSDReader::getAvgData(unsigned int phase, unsigned int local_id) const
369 : {
370 27533 : return _avg_data[indexFromIndex(_global_id[phase][local_id])];
371 : }
372 :
373 : unsigned int
374 1814780 : EBSDReader::getGrainNum() const
375 : {
376 1814780 : return _grain_num;
377 : }
378 :
379 : unsigned int
380 735836 : EBSDReader::getGrainNum(unsigned int phase) const
381 : {
382 735836 : return _global_id[phase].size();
383 : }
384 :
385 : unsigned int
386 1392687 : 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 1392687 : x_index = (unsigned int)((p(0) - _minx) / _dx);
393 1392687 : y_index = (unsigned int)((p(1) - _miny) / _dy);
394 1392687 : 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 1392687 : 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 1392687 : 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 1392687 : return global_index;
417 : }
418 :
419 : unsigned int
420 3303131 : EBSDReader::indexFromIndex(unsigned int var) const
421 : {
422 :
423 : // Transfer the index into the _avg_data array.
424 3303131 : unsigned avg_index = var;
425 :
426 : // Don't access out of range!
427 3303131 : 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 3303131 : return avg_index;
434 : }
435 :
436 : const std::map<dof_id_type, std::vector<Real>> &
437 113 : EBSDReader::getNodeToGrainWeightMap() const
438 : {
439 113 : return _node_to_grain_weight_map;
440 : }
441 :
442 : const std::map<dof_id_type, std::vector<Real>> &
443 20 : EBSDReader::getNodeToPhaseWeightMap() const
444 : {
445 20 : return _node_to_phase_weight_map;
446 : }
447 :
448 : unsigned int
449 968832 : EBSDReader::getGlobalID(unsigned int feature_id) const
450 : {
451 : auto it = _global_id_map.find(feature_id);
452 968832 : if (it == _global_id_map.end())
453 0 : mooseError("Invalid Feature ID");
454 968832 : return it->second;
455 : }
456 :
457 : void
458 12 : EBSDReader::meshChanged()
459 : {
460 : // maps are only rebuild for use in initial conditions, which happens in time step zero
461 12 : if (_time_step == 0)
462 12 : buildNodeWeightMaps();
463 12 : }
464 :
465 : void
466 137 : 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 137 : _mesh.nodeToActiveSemilocalElemMap();
473 137 : 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 475185 : for (const auto & node : as_range(mesh.active_nodes_begin(), mesh.active_nodes_end()))
477 : {
478 : // Get node_id
479 237387 : const dof_id_type node_id = node->id();
480 :
481 : // Initialize map entries for current node
482 237387 : _node_to_grain_weight_map[node_id].assign(getGrainNum(), 0.0);
483 237387 : _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 237387 : if (node_to_elem_pair != node_to_elem_map.end())
489 : {
490 : unsigned int n_elems =
491 : node_to_elem_pair->second
492 202366 : .size(); // n_elems can range from 1 to 4 for 2D and 1 to 8 for 3D problems
493 :
494 973842 : for (unsigned int ne = 0; ne < n_elems; ++ne)
495 : {
496 : // Current element index
497 771476 : unsigned int elem_id = (node_to_elem_pair->second)[ne];
498 :
499 : // Retrieve EBSD grain number for the current element index
500 771476 : const Elem * elem = mesh.elem_ptr(elem_id);
501 771476 : const EBSDReader::EBSDPointData & d = getData(elem->vertex_average());
502 :
503 : // get the (global) grain ID for the EBSD feature ID
504 771476 : const unsigned int global_id = getGlobalID(d._feature_id);
505 :
506 : // Calculate eta value and add to map
507 771476 : _node_to_grain_weight_map[node_id][global_id] += 1.0 / n_elems;
508 771476 : _node_to_phase_weight_map[node_id][d._phase] += 1.0 / n_elems;
509 : }
510 : }
511 137 : }
512 137 : }
513 :
514 : MooseSharedPointer<EBSDAccessFunctors::EBSDPointDataFunctor>
515 242 : EBSDReader::getPointDataAccessFunctor(const MooseEnum & field_name) const
516 : {
517 : EBSDPointDataFunctor * ret_val = NULL;
518 :
519 242 : switch (field_name)
520 : {
521 46 : case 0: // phi1
522 46 : ret_val = new EBSDPointDataPhi1();
523 : break;
524 46 : case 1: // phi
525 46 : ret_val = new EBSDPointDataPhi();
526 : break;
527 46 : case 2: // phi2
528 46 : ret_val = new EBSDPointDataPhi2();
529 : break;
530 82 : case 3: // grain
531 82 : ret_val = new EBSDPointDataFeatureID();
532 : break;
533 14 : case 4: // phase
534 14 : ret_val = new EBSDPointDataPhase();
535 : break;
536 8 : case 5: // symmetry
537 8 : 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 242 : return MooseSharedPointer<EBSDPointDataFunctor>(ret_val);
558 : }
559 :
560 : MooseSharedPointer<EBSDAccessFunctors::EBSDAvgDataFunctor>
561 24 : EBSDReader::getAvgDataAccessFunctor(const MooseEnum & field_name) const
562 : {
563 : EBSDAvgDataFunctor * ret_val = NULL;
564 :
565 24 : 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 24 : case 6: // feature_id
586 24 : 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 24 : return MooseSharedPointer<EBSDAvgDataFunctor>(ret_val);
607 : }
|