VertexCFD  0.0-dev
VertexCFD_Closure_ExternalInterpolation_impl.hpp
1 #ifndef VERTEXCFD_CLOSURE_EXTERNALINTERPOLATION_IMPL_HPP
2 #define VERTEXCFD_CLOSURE_EXTERNALINTERPOLATION_IMPL_HPP
3 
4 #include <Panzer_HierarchicParallelism.hpp>
5 #include <Panzer_Workset_Utilities.hpp>
6 
7 #include <exodusII.h>
8 
9 #include <ArborX_InterpMovingLeastSquares.hpp>
10 
11 namespace VertexCFD
12 {
13 namespace ClosureModel
14 {
15 
16 namespace Impl
17 {
18 // Copy unique source coordinates to the device in a format
19 // compatible with ArborX. We can't use lambdas in constructors with Cuda so we
20 // have to explicitly define a functor.
21 template<typename SourceView, typename CoordsView, int NumSpaceDim>
23 {
24  KOKKOS_FUNCTION void operator()(int i) const
25  {
26  for (int dim = 0; dim < NumSpaceDim; ++dim)
27  _source_points(i)[dim] = _coords(i, dim);
28  }
29 
30  SourceView _source_points;
31  CoordsView _coords;
32 };
33 } // namespace Impl
34 
35 //---------------------------------------------------------------------------//
36 template<class EvalType, class Traits, int NumSpaceDim>
37 ExternalInterpolation<EvalType, Traits, NumSpaceDim>::ExternalInterpolation(
38  const panzer::IntegrationRule& ir,
39  const Teuchos::ParameterList& closure_params)
40  : _ir_degree(ir.cubature_degree)
41  , _interpolated_values("ExternalInterpolation::interpolated_values", 0)
42 {
43  const std::string output_field_name
44  = closure_params.get<std::string>("output field name");
45  _scalar_field = PHX::MDField<scalar_type, panzer::Cell, panzer::Point>(
46  output_field_name, ir.dl_scalar);
47  const std::string input_field_name
48  = closure_params.get<std::string>("input field name");
49  _file_name = closure_params.get<std::string>("file name");
50 
51  this->addEvaluatedField(_scalar_field);
52 
53  _name = std::string("External Interpolation ")
54  + std::to_string(NumSpaceDim) + "D" + " for " + output_field_name;
55  this->setName(_name);
56 
57  // We read in the input file to extract the source coordinates, deduplicate
58  // them, and then allocate memory for the source nodal values. Reading in
59  // the nodal values is left to evaluateFields since that depends on the
60  // time step we are interested in.
61 
62  float version;
63  int CPU_word_size = sizeof(double);
64  int IO_word_size = 0; /* use what is stored in file */
65  const int exoid = ex_open(
66  _file_name.c_str(), EX_READ, &CPU_word_size, &IO_word_size, &version);
67 
68  const std::size_t num_nodes = ex_inquire_int(exoid, EX_INQ_NODES);
69 
70  const Kokkos::View<double* [NumSpaceDim], Kokkos::LayoutLeft, Kokkos::HostSpace>
71  coords_host(Kokkos::view_alloc(Kokkos::WithoutInitializing,
72  "ExternalInterpolation::coords"),
73  num_nodes);
74 
75  int error = ex_get_coord(
76  exoid,
77  Kokkos::subview(coords_host, Kokkos::ALL, 0).data(),
78  Kokkos::subview(coords_host, Kokkos::ALL, 1).data(),
79  NumSpaceDim == 2 ? nullptr
80  : Kokkos::subview(coords_host, Kokkos::ALL, 2).data());
81  if (error != 0)
82  {
83  const std::string message = _name + ": Error reading coordinates!";
84  Kokkos::abort(message.c_str());
85  }
86 
87  int num_nodal_vars;
88  error = ex_get_variable_param(exoid, EX_NODAL, &num_nodal_vars);
89  if (error != 0)
90  {
91  const std::string message = _name + ": Error getting variable params!";
92  Kokkos::abort(message.c_str());
93  }
94 
95  const int name_len = ex_inquire_int(exoid, EX_INQ_MAX_READ_NAME_LENGTH);
96 
97  std::vector<char> var_names_buffer(num_nodal_vars * (name_len + 1));
98  std::vector<char*> var_names(num_nodal_vars);
99  for (int i = 0; i < num_nodal_vars; ++i)
100  var_names[i] = var_names_buffer.data() + i * (name_len + 1);
101 
102  error = ex_get_variable_names(
103  exoid, EX_NODAL, num_nodal_vars, var_names.data());
104  if (error != 0)
105  {
106  const std::string message = _name + ": Error getting variable names!";
107  Kokkos::abort(message.c_str());
108  }
109 
110  const auto it
111  = std::find(var_names.begin(), var_names.end(), input_field_name);
112  if (it == var_names.end())
113  {
114  std::stringstream ss;
115  ss << _name + ": Error finding field with name <" << input_field_name
116  << ">. Available fields are: ";
117  for (auto& file_field_name : var_names)
118  ss << "- <" << file_field_name << ">\n";
119  Kokkos::abort(ss.str().c_str());
120  }
121  _var_index = std::distance(var_names.begin(), it) + 1;
122 
123  _nodal_values_host = Kokkos::View<double*, Kokkos::HostSpace>(
124  Kokkos::view_alloc(Kokkos::WithoutInitializing,
125  "ExternalInterpolation::nodal_values"),
126  num_nodes);
127 
128  int num_time_steps = ex_inquire_int(exoid, EX_INQ_TIME);
129  _time_values.resize(num_time_steps);
130  error = ex_get_all_times(exoid, _time_values.data());
131  if (error != 0)
132  {
133  const std::string message = _name
134  + ": Error reading extracting time steps!";
135  Kokkos::abort(message.c_str());
136  }
137 
138  error = ex_close(exoid);
139  if (error != 0)
140  {
141  const std::string message = _name + ": Error closing exodus file!";
142  Kokkos::abort(message.c_str());
143  }
144 
145  // Deduplicate source coordinates and store the mapping
146  _reduced_to_global.resize(num_nodes);
147  std::iota(
148  _reduced_to_global.begin(), _reduced_to_global.end(), std::size_t(0));
149  // lexicographic comparison
150  std::sort(_reduced_to_global.begin(),
151  _reduced_to_global.end(),
152  [&](std::size_t i, std::size_t j) {
153  for (int dim = 0; dim < NumSpaceDim; ++dim)
154  {
155  if (coords_host(i, dim) < coords_host(j, dim))
156  return true;
157  if (coords_host(i, dim) > coords_host(j, dim))
158  return false;
159  }
160  return false;
161  });
162  _reduced_to_global.erase(
163  std::unique(_reduced_to_global.begin(),
164  _reduced_to_global.end(),
165  [&](std::size_t i, std::size_t j) {
166  for (int dim = 0; dim < NumSpaceDim; ++dim)
167  if (coords_host(i, dim) != coords_host(j, dim))
168  return false;
169  return true;
170  }),
171  _reduced_to_global.end());
172  const std::size_t num_unique_nodes = _reduced_to_global.size();
173 
174  const Kokkos::View<double* [NumSpaceDim], Kokkos::LayoutLeft, Kokkos::HostSpace>
175  coords_host_reduced(Kokkos::view_alloc(Kokkos::WithoutInitializing,
176  "ExternalInterpolation::"
177  "coords"),
178  num_unique_nodes);
179 
180  _nodal_values_host_reduced = Kokkos::View<double*, Kokkos::HostSpace>(
181  Kokkos::view_alloc(Kokkos::WithoutInitializing,
182  "ExternalInterpolation::nodal_values"),
183  num_unique_nodes);
184 
185  for (size_t i = 0; i < num_unique_nodes; ++i)
186  {
187  for (int dim = 0; dim < NumSpaceDim; ++dim)
188  coords_host_reduced(i, dim)
189  = coords_host(_reduced_to_global[i], dim);
190  }
191 
192  // Copy unique source coordinates and values to the device in a format
193  // compatible with ArborX. We can't use lambdas in constructors with Cuda
194  // so we have to explicitly define a functor.
195  _source_points = Kokkos::View<
196  ArborX::ExperimentalHyperGeometry::Point<NumSpaceDim, double>*,
197  MemorySpace>(
198  Kokkos::view_alloc(Kokkos::WithoutInitializing, "source_points"),
199  num_unique_nodes);
200  const auto coords = Kokkos::create_mirror_view_and_copy(
201  MemorySpace{}, coords_host_reduced);
202  _nodal_values = Kokkos::create_mirror_view(MemorySpace{},
203  _nodal_values_host_reduced);
204 
205  ExecutionSpace exec_space;
206  Kokkos::parallel_for(
207  Kokkos::RangePolicy<ExecutionSpace>(exec_space, 0, num_unique_nodes),
208  Impl::ConvertExodusToArborXLayout<decltype(_source_points),
209  decltype(coords),
210  NumSpaceDim>{_source_points, coords});
211  exec_space.fence();
212 
213  read_node_values(0);
214 }
215 
216 //---------------------------------------------------------------------------//
217 template<class EvalType, class Traits, int NumSpaceDim>
218 void ExternalInterpolation<EvalType, Traits, NumSpaceDim>::read_node_values(
219  size_t time_step)
220 {
221  float version;
222  int CPU_word_size = sizeof(double);
223  int IO_word_size = 0; /* use what is stored in file */
224  const int exoid = ex_open(
225  _file_name.c_str(), EX_READ, &CPU_word_size, &IO_word_size, &version);
226 
227  int error = ex_get_var(exoid,
228  time_step + 1,
229  EX_NODAL,
230  _var_index,
231  /*obj_id*/ 1,
232  _nodal_values_host.size(),
233  _nodal_values_host.data());
234 
235  if (error != 0)
236  {
237  const std::string message = _name + ": Error reading nodal values!";
238  Kokkos::abort(message.c_str());
239  }
240  error = ex_close(exoid);
241  if (error != 0)
242  {
243  const std::string message = _name + ": Error closing exodus file!";
244  Kokkos::abort(message.c_str());
245  }
246 
247  for (size_t i = 0; i < _reduced_to_global.size(); ++i)
248  _nodal_values_host_reduced(i)
249  = _nodal_values_host(_reduced_to_global[i]);
250 
251  ExecutionSpace exec;
252  Kokkos::deep_copy(exec, _nodal_values, _nodal_values_host_reduced);
253  exec.fence();
254 }
255 
256 //---------------------------------------------------------------------------//
257 template<class EvalType, class Traits, int NumSpaceDim>
258 void ExternalInterpolation<EvalType, Traits, NumSpaceDim>::postRegistrationSetup(
259  typename Traits::SetupData sd, PHX::FieldManager<Traits>&)
260 {
261  _ir_index = panzer::getIntegrationRuleIndex(_ir_degree, (*sd.worksets_)[0]);
262 }
263 
264 //---------------------------------------------------------------------------//
265 template<class EvalType, class Traits, int NumSpaceDim>
266 void ExternalInterpolation<EvalType, Traits, NumSpaceDim>::evaluateFields(
267  typename Traits::EvalData workset)
268 {
269  // Find the time step index such that the corresponding time step is
270  // greater than workset.time. If no such index exists, take the last one.
271  size_t time_step_index = std::min<int>(
272  (std::lower_bound(_time_values.begin(), _time_values.end(), workset.time)
273  - _time_values.begin()),
274  _time_values.size() - 1);
275 
276  // Choose the time step index that is closest to the solver time. This can
277  // be the one selected previously or the one before that if the index isn't
278  // zero.
279  if ((time_step_index != 0)
280  && (workset.time - _time_values[time_step_index - 1])
281  < (_time_values[time_step_index] - workset.time))
282  --time_step_index;
283 
284  if (time_step_index != _last_time_step_index)
285  read_node_values(time_step_index);
286 
287  _last_time_step_index = time_step_index;
288 
289  const auto ip_coords = workset.int_rules[_ir_index]->ip_coordinates;
290  const int n_points = ip_coords.extent(1);
291  const int n_cells = workset.num_cells;
292 
293  ExecutionSpace exec_space;
294 
295  const Kokkos::View<
296  ArborX::ExperimentalHyperGeometry::Point<NumSpaceDim, double>*,
297  MemorySpace>
298  target_points(Kokkos::view_alloc(Kokkos::WithoutInitializing,
299  "ExternalInterpolation::targets"),
300  n_cells * n_points);
301  Kokkos::parallel_for(
302  Kokkos::MDRangePolicy<ExecutionSpace, Kokkos::Rank<2>>(
303  exec_space, {0, 0}, {n_cells, n_points}),
304  KOKKOS_LAMBDA(int cell, int point) {
305  for (int dim = 0; dim < NumSpaceDim; ++dim)
306  target_points(cell * n_points + point)[dim]
307  = ip_coords(cell, point, dim);
308  });
309 
310  const ArborX::Interpolation::MovingLeastSquares<MemorySpace> mls(
311  exec_space, _source_points, target_points);
312 
313  Kokkos::realloc(Kokkos::view_alloc(Kokkos::WithoutInitializing),
314  _interpolated_values,
315  target_points.size());
316 
317  mls.interpolate(exec_space, _nodal_values, _interpolated_values);
318  exec_space.fence();
319 
320  const auto policy
321  = panzer::HP::inst().teamPolicy<scalar_type, PHX::Device>(n_cells);
322  Kokkos::parallel_for(this->getName(), policy, *this);
323 }
324 
325 //---------------------------------------------------------------------------//
326 template<class EvalType, class Traits, int NumSpaceDim>
327 KOKKOS_INLINE_FUNCTION void
328 ExternalInterpolation<EvalType, Traits, NumSpaceDim>::operator()(
329  const Kokkos::TeamPolicy<PHX::exec_space>::member_type& team) const
330 {
331  const int cell = team.league_rank();
332  const int num_point = _scalar_field.extent(1);
333 
334  Kokkos::parallel_for(
335  Kokkos::TeamThreadRange(team, 0, num_point), [&](const int point) {
336  _scalar_field(cell, point)
337  = _interpolated_values(cell * num_point + point);
338  });
339 }
340 
341 //---------------------------------------------------------------------------//
342 } // namespace ClosureModel
343 } // end namespace VertexCFD
344 
345 #endif // end VERTEXCFD_CLOSURE_EXTERNALINTERPOLATION_IMPL_HPP
VertexCFD
Definition: tstMethodManufacturedSolutionBC.cpp:23
VertexCFD::ClosureModel::Impl::ConvertExodusToArborXLayout
Definition: VertexCFD_Closure_ExternalInterpolation_impl.hpp:23