Line data Source code
1 : // rbOOmit: An implementation of the Certified Reduced Basis method.
2 : // Copyright (C) 2009, 2010 David J. Knezevic
3 :
4 : // This file is part of rbOOmit.
5 :
6 : // rbOOmit is free software; you can redistribute it and/or
7 : // modify it under the terms of the GNU Lesser General Public
8 : // License as published by the Free Software Foundation; either
9 : // version 2.1 of the License, or (at your option) any later version.
10 :
11 : // rbOOmit is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : // Lesser General Public License for more details.
15 :
16 : // You should have received a copy of the GNU Lesser General Public
17 : // License along with this library; if not, write to the Free Software
18 : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 :
20 : // rbOOmit includes
21 : #include "libmesh/rb_eim_evaluation.h"
22 : #include "libmesh/rb_eim_theta.h"
23 : #include "libmesh/rb_parametrized_function.h"
24 : #include "libmesh/rb_evaluation.h"
25 : #include "libmesh/utility.h" // Utility::mkdir
26 :
27 : // libMesh includes
28 : #include "libmesh/xdr_cxx.h"
29 : #include "libmesh/libmesh_logging.h"
30 : #include "libmesh/replicated_mesh.h"
31 : #include "libmesh/elem.h"
32 : #include "libmesh/system.h"
33 : #include "libmesh/equation_systems.h"
34 : #include "libmesh/numeric_vector.h"
35 : #include "libmesh/quadrature.h"
36 : #include "libmesh/boundary_info.h"
37 : #include "timpi/parallel_implementation.h"
38 :
39 : // C++ includes
40 : #include <sstream>
41 : #include <fstream>
42 : #include <numeric> // std::accumulate
43 : #include <iterator> // std::advance
44 :
45 : namespace libMesh
46 : {
47 :
48 0 : RBEIMEvaluation::RBEIMEvaluation(const Parallel::Communicator & comm)
49 : :
50 : ParallelObject(comm),
51 0 : eim_error_indicator_normalization(RBEIMEvaluation::MAX_RHS),
52 0 : limit_eim_error_indicator_to_one(true),
53 0 : _rb_eim_solves_N(0),
54 0 : _preserve_rb_eim_solutions(false),
55 0 : _is_eim_error_indicator_active(false)
56 : {
57 0 : }
58 :
59 0 : RBEIMEvaluation::~RBEIMEvaluation() = default;
60 :
61 0 : void RBEIMEvaluation::clear()
62 : {
63 0 : _vec_eval_input.clear();
64 :
65 0 : _interpolation_points_comp.clear();
66 0 : _interpolation_points_spatial_indices.clear();
67 :
68 :
69 0 : _interpolation_matrix.resize(0,0);
70 :
71 : // Delete any RBTheta objects that were created
72 0 : _rb_eim_theta_objects.clear();
73 0 : }
74 :
75 0 : void RBEIMEvaluation::resize_data_structures(const unsigned int Nmax)
76 : {
77 : // Resize the data structures relevant to the EIM system
78 0 : _vec_eval_input.clear();
79 0 : _vec_eval_input.all_xyz.clear();
80 :
81 0 : _interpolation_points_comp.clear();
82 0 : _interpolation_points_spatial_indices.clear();
83 :
84 0 : _interpolation_matrix.resize(Nmax,Nmax);
85 0 : }
86 :
87 0 : void RBEIMEvaluation::set_parametrized_function(std::unique_ptr<RBParametrizedFunction> pf)
88 : {
89 0 : _parametrized_function = std::move(pf);
90 0 : }
91 :
92 0 : RBParametrizedFunction & RBEIMEvaluation::get_parametrized_function()
93 : {
94 0 : libmesh_error_msg_if(!_parametrized_function, "Parametrized function not initialized yet");
95 :
96 0 : return *_parametrized_function;
97 : }
98 :
99 0 : const RBParametrizedFunction & RBEIMEvaluation::get_parametrized_function() const
100 : {
101 0 : libmesh_error_msg_if(!_parametrized_function, "Parametrized function not initialized yet");
102 :
103 0 : return *_parametrized_function;
104 : }
105 :
106 0 : DenseVector<Number> RBEIMEvaluation::rb_eim_solve(DenseVector<Number> & EIM_rhs)
107 : {
108 0 : LOG_SCOPE("rb_eim_solve()", "RBEIMEvaluation");
109 :
110 0 : libmesh_error_msg_if(EIM_rhs.size() > get_n_basis_functions(),
111 : "Error: N cannot be larger than the number of basis functions in rb_solve");
112 :
113 0 : libmesh_error_msg_if(EIM_rhs.size()==0, "Error: N must be greater than 0 in rb_solve");
114 :
115 0 : const unsigned int N = EIM_rhs.size();
116 0 : DenseVector<Number> rb_eim_solution(N);
117 0 : DenseMatrix<Number> interpolation_matrix_N;
118 0 : _interpolation_matrix.get_principal_submatrix(N, interpolation_matrix_N);
119 :
120 0 : interpolation_matrix_N.lu_solve(EIM_rhs, rb_eim_solution);
121 :
122 0 : return rb_eim_solution;
123 0 : }
124 :
125 0 : void RBEIMEvaluation::rb_eim_solves(const std::vector<RBParameters> & mus,
126 : unsigned int N)
127 : {
128 0 : if (_preserve_rb_eim_solutions)
129 : {
130 : // In this case we preserve _rb_eim_solutions and hence we
131 : // just return immediately so that we skip updating
132 : // _rb_eim_solutions below. This is relevant in cases where
133 : // we set up _rb_eim_solutions elsewhere and we don't want
134 : // to override it.
135 0 : return;
136 : }
137 :
138 0 : libmesh_error_msg_if(N > get_n_basis_functions(),
139 : "Error: N cannot be larger than the number of basis functions in rb_eim_solves");
140 0 : libmesh_error_msg_if(N==0, "Error: N must be greater than 0 in rb_eim_solves");
141 :
142 : // If mus and N are the same as before, then we return early
143 0 : if ((_rb_eim_solves_mus == mus) && (_rb_eim_solves_N == N))
144 0 : return;
145 :
146 0 : LOG_SCOPE("rb_eim_solves()", "RBEIMEvaluation");
147 :
148 0 : _rb_eim_solves_mus = mus;
149 0 : _rb_eim_solves_N = N;
150 :
151 0 : if (get_parametrized_function().is_lookup_table)
152 : {
153 0 : _rb_eim_solutions.resize(mus.size());
154 0 : for (auto mu_index : index_range(mus))
155 : {
156 : Real lookup_table_param =
157 0 : mus[mu_index].get_value(get_parametrized_function().lookup_table_param_name);
158 :
159 : // Cast lookup_table_param to an unsigned integer so that we can use
160 : // it as an index into the EIM rhs values obtained from the lookup table.
161 : unsigned int lookup_table_index =
162 0 : cast_int<unsigned int>(std::round(lookup_table_param));
163 :
164 0 : DenseVector<Number> values;
165 0 : _eim_solutions_for_training_set[lookup_table_index].get_principal_subvector(N, values);
166 0 : _rb_eim_solutions[mu_index] = values;
167 : }
168 :
169 0 : return;
170 : }
171 :
172 : // output all comps indexing is as follows:
173 : // mu index --> interpolation point index --> component index --> value.
174 0 : std::vector<std::vector<std::vector<Number>>> output_all_comps;
175 0 : if (get_parametrized_function().on_mesh_sides())
176 0 : get_parametrized_function().side_vectorized_evaluate(mus, _vec_eval_input, output_all_comps);
177 0 : else if (get_parametrized_function().on_mesh_nodes())
178 0 : get_parametrized_function().node_vectorized_evaluate(mus, _vec_eval_input, output_all_comps);
179 : else
180 0 : get_parametrized_function().vectorized_evaluate(mus, _vec_eval_input, output_all_comps);
181 :
182 : // Previously we did one RB-EIM solve per input mu, but now we do
183 : // one RB-EIM solve per input mu, per sample. In order for this to
184 : // work, we require that all the input mu objects have the same
185 : // number of samples.
186 0 : auto n_samples_0 = mus[0].n_samples();
187 0 : for (const auto & mu : mus)
188 0 : libmesh_error_msg_if(mu.n_samples() != n_samples_0, "All RBParameters objects must have same n_samples()");
189 :
190 : // After we verified that all mus have the same number of samples,
191 : // the total number of RB-EIM solves is simply the number of mus
192 : // times the number of samples.
193 0 : unsigned int num_rb_eim_solves = mus.size() * n_samples_0;
194 :
195 : // A special case is when we are passed a single RBParameters object
196 : // with no parameters stored on it. In this case, we effectively
197 : // have Theta(mu) == const, and therefore we still need to do at
198 : // least one RB-EIM solve. In this case, we require that there is
199 : // only one entry in "mus" for simplicity.
200 0 : if (num_rb_eim_solves == 0 && mus[0].n_parameters() == 0)
201 : {
202 0 : libmesh_error_msg_if(mus.size() != 1, "Must pass in only a single RBParameters object when solving with no parameters.");
203 0 : num_rb_eim_solves = 1;
204 : }
205 :
206 0 : std::vector<std::vector<Number>> evaluated_values_at_interp_points(num_rb_eim_solves);
207 :
208 0 : std::vector<Number> evaluated_values_at_err_indicator_point;
209 0 : if (_is_eim_error_indicator_active)
210 0 : evaluated_values_at_err_indicator_point.resize(num_rb_eim_solves);
211 :
212 : // In this loop, counter goes from 0 to num_rb_eim_solves. The
213 : // purpose of this loop is to strip out the "columns" of the
214 : // output_all_comps array into rows.
215 : {
216 0 : unsigned int counter = 0;
217 0 : for (auto mu_index : index_range(mus))
218 0 : for (auto sample_index : make_range(mus[mu_index].n_samples()))
219 : {
220 : // Ignore compiler warnings about unused loop index
221 0 : libmesh_ignore(sample_index);
222 :
223 0 : evaluated_values_at_interp_points[counter].resize(N);
224 :
225 0 : for (unsigned int interp_pt_index=0; interp_pt_index<N; interp_pt_index++)
226 : {
227 0 : unsigned int comp = _interpolation_points_comp[interp_pt_index];
228 :
229 : // This line of code previously used "mu_index", now we use
230 : // "counter" handle the multi-sample RBParameters case.
231 0 : evaluated_values_at_interp_points[counter][interp_pt_index] =
232 0 : output_all_comps[counter][interp_pt_index][comp];
233 : }
234 :
235 0 : if (_is_eim_error_indicator_active)
236 : {
237 0 : unsigned int comp = _interpolation_points_comp[N];
238 :
239 0 : evaluated_values_at_err_indicator_point[counter] =
240 0 : output_all_comps[counter][N][comp];
241 : }
242 :
243 0 : counter++;
244 : }
245 :
246 : // Throw an error if we didn't do the required number of solves for
247 : // some reason
248 0 : libmesh_error_msg_if(counter != num_rb_eim_solves,
249 : "We should have done " << num_rb_eim_solves <<
250 : " solves, instead we did " << counter);
251 : }
252 :
253 0 : DenseMatrix<Number> interpolation_matrix_N;
254 0 : _interpolation_matrix.get_principal_submatrix(N, interpolation_matrix_N);
255 :
256 : // The number of RB EIM solutions is equal to the size of the
257 : // "evaluated_values_at_interp_points" vector which we determined
258 : // earlier.
259 0 : _rb_eim_solutions.resize(num_rb_eim_solves);
260 0 : if (_is_eim_error_indicator_active)
261 0 : _rb_eim_error_indicators.resize(num_rb_eim_solves);
262 :
263 : {
264 0 : unsigned int counter = 0;
265 0 : for (auto mu_index : index_range(mus))
266 0 : for (auto sample_index : make_range(mus[mu_index].n_samples()))
267 : {
268 : // Ignore compiler warnings about unused loop index
269 0 : libmesh_ignore(sample_index);
270 :
271 0 : DenseVector<Number> EIM_rhs = evaluated_values_at_interp_points[counter];
272 0 : interpolation_matrix_N.lu_solve(EIM_rhs, _rb_eim_solutions[counter]);
273 :
274 : // If we're using the EIM error indicator, then we compute it via the approach
275 : // proposed in Proposition 3.3 of "An empirical interpolation method: application
276 : // to efficient reduced-basis discretization of partial differential equations",
277 : // Barrault et al.
278 0 : if (_is_eim_error_indicator_active)
279 : {
280 0 : Number error_indicator_rhs = evaluated_values_at_err_indicator_point[counter];
281 0 : _rb_eim_error_indicators[counter] =
282 0 : get_eim_error_indicator(
283 0 : error_indicator_rhs, _rb_eim_solutions[counter], EIM_rhs);
284 : }
285 :
286 0 : counter++;
287 : }
288 : }
289 0 : }
290 :
291 0 : void RBEIMEvaluation::initialize_interpolation_points_spatial_indices()
292 : {
293 0 : _interpolation_points_spatial_indices.clear();
294 :
295 0 : get_parametrized_function().get_spatial_indices(_interpolation_points_spatial_indices,
296 0 : _vec_eval_input);
297 0 : }
298 :
299 0 : void RBEIMEvaluation::initialize_param_fn_spatial_indices()
300 : {
301 0 : get_parametrized_function().initialize_spatial_indices(_interpolation_points_spatial_indices,
302 0 : _vec_eval_input);
303 0 : }
304 :
305 0 : unsigned int RBEIMEvaluation::get_n_basis_functions() const
306 : {
307 0 : if (get_parametrized_function().on_mesh_sides())
308 0 : return _local_side_eim_basis_functions.size();
309 0 : else if (get_parametrized_function().on_mesh_nodes())
310 0 : return _local_node_eim_basis_functions.size();
311 : else
312 0 : return _local_eim_basis_functions.size();
313 : }
314 :
315 0 : unsigned int RBEIMEvaluation::get_n_interpolation_points() const
316 : {
317 0 : return _vec_eval_input.all_xyz.size();
318 : }
319 :
320 0 : unsigned int RBEIMEvaluation::get_n_elems() const
321 : {
322 0 : return _vec_eval_input.elem_id_to_local_index.size();
323 : }
324 :
325 0 : unsigned int RBEIMEvaluation::get_n_properties() const
326 : {
327 0 : return _vec_eval_input.rb_property_map.size();
328 : }
329 :
330 0 : void RBEIMEvaluation::set_n_basis_functions(unsigned int n_bfs)
331 : {
332 0 : if (get_parametrized_function().on_mesh_sides())
333 0 : _local_side_eim_basis_functions.resize(n_bfs);
334 0 : else if (get_parametrized_function().on_mesh_nodes())
335 0 : _local_node_eim_basis_functions.resize(n_bfs);
336 : else
337 0 : _local_eim_basis_functions.resize(n_bfs);
338 0 : }
339 :
340 0 : void RBEIMEvaluation::decrement_vector(QpDataMap & v,
341 : const DenseVector<Number> & coeffs)
342 : {
343 0 : LOG_SCOPE("decrement_vector()", "RBEIMEvaluation");
344 :
345 0 : libmesh_error_msg_if(get_n_basis_functions() != coeffs.size(),
346 : "Error: Number of coefficients should match number of basis functions");
347 :
348 0 : for (auto & [elem_id, v_comp_and_qp] : v)
349 : {
350 0 : for (const auto & comp : index_range(v_comp_and_qp))
351 0 : for (unsigned int qp : index_range(v_comp_and_qp[comp]))
352 0 : for (unsigned int i : index_range(_local_eim_basis_functions))
353 : {
354 : // Check that entry (elem_id,comp,qp) exists in _local_eim_basis_functions so that
355 : // we get a clear error message if there is any missing data
356 0 : const auto & basis_comp_and_qp = libmesh_map_find(_local_eim_basis_functions[i], elem_id);
357 :
358 0 : libmesh_error_msg_if(comp >= basis_comp_and_qp.size(), "Error: Invalid comp");
359 0 : libmesh_error_msg_if(qp >= basis_comp_and_qp[comp].size(), "Error: Invalid qp");
360 :
361 0 : v_comp_and_qp[comp][qp] -= coeffs(i) * basis_comp_and_qp[comp][qp];
362 : }
363 : }
364 0 : }
365 :
366 0 : void RBEIMEvaluation::side_decrement_vector(SideQpDataMap & v,
367 : const DenseVector<Number> & coeffs)
368 : {
369 0 : LOG_SCOPE("side_decrement_vector()", "RBEIMEvaluation");
370 :
371 0 : libmesh_error_msg_if(get_n_basis_functions() != coeffs.size(),
372 : "Error: Number of coefficients should match number of basis functions");
373 :
374 0 : for (auto & [elem_and_side, v_comp_and_qp] : v)
375 : {
376 0 : for (const auto & comp : index_range(v_comp_and_qp))
377 0 : for (unsigned int qp : index_range(v_comp_and_qp[comp]))
378 0 : for (unsigned int i : index_range(_local_side_eim_basis_functions))
379 : {
380 : // Check that entry (elem_and_side,comp,qp) exists in _local_side_eim_basis_functions so that
381 : // we get a clear error message if there is any missing data
382 0 : const auto & basis_comp_and_qp = libmesh_map_find(_local_side_eim_basis_functions[i], elem_and_side);
383 :
384 0 : libmesh_error_msg_if(comp >= basis_comp_and_qp.size(), "Error: Invalid comp");
385 0 : libmesh_error_msg_if(qp >= basis_comp_and_qp[comp].size(), "Error: Invalid qp");
386 :
387 0 : v_comp_and_qp[comp][qp] -= coeffs(i) * basis_comp_and_qp[comp][qp];
388 : }
389 : }
390 0 : }
391 :
392 0 : void RBEIMEvaluation::node_decrement_vector(NodeDataMap & v,
393 : const DenseVector<Number> & coeffs)
394 : {
395 0 : LOG_SCOPE("node_decrement_vector()", "RBEIMEvaluation");
396 :
397 0 : libmesh_error_msg_if(get_n_basis_functions() != coeffs.size(),
398 : "Error: Number of coefficients should match number of basis functions");
399 :
400 0 : for (auto & [node_id, v_comps] : v)
401 : {
402 0 : for (const auto & comp : index_range(v_comps))
403 0 : for (unsigned int i : index_range(_local_node_eim_basis_functions))
404 : {
405 : // Check that entry (node_id,comp) exists in _local_node_eim_basis_functions so that
406 : // we get a clear error message if there is any missing data
407 0 : const auto & basis_comp = libmesh_map_find(_local_node_eim_basis_functions[i], node_id);
408 :
409 0 : libmesh_error_msg_if(comp >= basis_comp.size(), "Error: Invalid comp");
410 :
411 0 : v_comps[comp] -= coeffs(i) * basis_comp[comp];
412 : }
413 : }
414 0 : }
415 :
416 0 : void RBEIMEvaluation::initialize_eim_theta_objects()
417 : {
418 : // Initialize the rb_theta objects that access the solution from this rb_eim_evaluation
419 0 : _rb_eim_theta_objects.clear();
420 0 : for (auto i : make_range(get_n_basis_functions()))
421 0 : _rb_eim_theta_objects.emplace_back(build_eim_theta(i));
422 0 : }
423 :
424 0 : std::vector<std::unique_ptr<RBTheta>> & RBEIMEvaluation::get_eim_theta_objects()
425 : {
426 0 : return _rb_eim_theta_objects;
427 : }
428 :
429 0 : std::unique_ptr<RBTheta> RBEIMEvaluation::build_eim_theta(unsigned int index)
430 : {
431 0 : return std::make_unique<RBEIMTheta>(*this, index);
432 : }
433 :
434 0 : void RBEIMEvaluation::get_parametrized_function_values_at_qps(
435 : const QpDataMap & pf,
436 : dof_id_type elem_id,
437 : unsigned int comp,
438 : std::vector<Number> & values)
439 : {
440 0 : LOG_SCOPE("get_parametrized_function_values_at_qps()", "RBEIMConstruction");
441 :
442 0 : values.clear();
443 :
444 0 : if (const auto it = pf.find(elem_id);
445 0 : it != pf.end())
446 : {
447 0 : const auto & comps_and_qps_on_elem = it->second;
448 0 : libmesh_error_msg_if(comp >= comps_and_qps_on_elem.size(),
449 : "Invalid comp index: " << comp);
450 :
451 0 : values = comps_and_qps_on_elem[comp];
452 : }
453 0 : }
454 :
455 0 : void RBEIMEvaluation::get_parametrized_function_side_values_at_qps(
456 : const SideQpDataMap & pf,
457 : dof_id_type elem_id,
458 : unsigned int side_index,
459 : unsigned int comp,
460 : std::vector<Number> & values)
461 : {
462 0 : LOG_SCOPE("get_parametrized_function_side_values_at_qps()", "RBEIMConstruction");
463 :
464 0 : values.clear();
465 :
466 0 : if (const auto it = pf.find(std::make_pair(elem_id, side_index));
467 0 : it != pf.end())
468 : {
469 0 : const auto & comps_and_qps_on_elem = it->second;
470 0 : libmesh_error_msg_if(comp >= comps_and_qps_on_elem.size(),
471 : "Invalid comp index: " << comp);
472 :
473 0 : values = comps_and_qps_on_elem[comp];
474 : }
475 0 : }
476 :
477 0 : Number RBEIMEvaluation::get_parametrized_function_node_local_value(
478 : const NodeDataMap & pf,
479 : dof_id_type node_id,
480 : unsigned int comp)
481 : {
482 0 : LOG_SCOPE("get_parametrized_function_node_local_value()", "RBEIMConstruction");
483 :
484 0 : if (const auto it = pf.find(node_id);
485 0 : it != pf.end())
486 : {
487 0 : const std::vector<Number> & vec = it->second;
488 0 : libmesh_error_msg_if (comp >= vec.size(), "Error: Invalid comp index");
489 0 : return vec[comp];
490 : }
491 : else
492 0 : return 0.;
493 : }
494 :
495 0 : Number RBEIMEvaluation::get_parametrized_function_value(
496 : const Parallel::Communicator & comm,
497 : const QpDataMap & pf,
498 : dof_id_type elem_id,
499 : unsigned int comp,
500 : unsigned int qp)
501 : {
502 0 : std::vector<Number> values;
503 0 : get_parametrized_function_values_at_qps(pf, elem_id, comp, values);
504 :
505 : // In parallel, values should only be non-empty on one processor
506 0 : Number value = 0.;
507 0 : if (!values.empty())
508 : {
509 0 : libmesh_error_msg_if(qp >= values.size(), "Error: Invalid qp index");
510 :
511 0 : value = values[qp];
512 : }
513 0 : comm.sum(value);
514 :
515 0 : return value;
516 : }
517 :
518 0 : Number RBEIMEvaluation::get_parametrized_function_side_value(
519 : const Parallel::Communicator & comm,
520 : const SideQpDataMap & pf,
521 : dof_id_type elem_id,
522 : unsigned int side_index,
523 : unsigned int comp,
524 : unsigned int qp)
525 : {
526 0 : std::vector<Number> values;
527 0 : get_parametrized_function_side_values_at_qps(pf, elem_id, side_index, comp, values);
528 :
529 : // In parallel, values should only be non-empty on one processor
530 0 : Number value = 0.;
531 0 : if (!values.empty())
532 : {
533 0 : libmesh_error_msg_if(qp >= values.size(), "Error: Invalid qp index");
534 :
535 0 : value = values[qp];
536 : }
537 0 : comm.sum(value);
538 :
539 0 : return value;
540 : }
541 :
542 0 : Number RBEIMEvaluation::get_parametrized_function_node_value(
543 : const Parallel::Communicator & comm,
544 : const NodeDataMap & pf,
545 : dof_id_type node_id,
546 : unsigned int comp)
547 : {
548 0 : LOG_SCOPE("get_parametrized_function_node_value()", "RBEIMConstruction");
549 :
550 0 : Number value = get_parametrized_function_node_local_value(pf, node_id, comp);
551 0 : comm.sum(value);
552 :
553 0 : return value;
554 : }
555 :
556 0 : void RBEIMEvaluation::get_eim_basis_function_values_at_qps(unsigned int basis_function_index,
557 : dof_id_type elem_id,
558 : unsigned int comp,
559 : std::vector<Number> & values) const
560 : {
561 0 : libmesh_error_msg_if(basis_function_index >= _local_eim_basis_functions.size(),
562 : "Invalid basis function index: " << basis_function_index);
563 :
564 0 : get_parametrized_function_values_at_qps(
565 0 : _local_eim_basis_functions[basis_function_index],
566 : elem_id,
567 : comp,
568 : values);
569 0 : }
570 :
571 0 : void RBEIMEvaluation::get_eim_basis_function_side_values_at_qps(unsigned int basis_function_index,
572 : dof_id_type elem_id,
573 : unsigned int side_index,
574 : unsigned int comp,
575 : std::vector<Number> & values) const
576 : {
577 0 : libmesh_error_msg_if(basis_function_index >= _local_side_eim_basis_functions.size(),
578 : "Invalid basis function index: " << basis_function_index);
579 :
580 0 : get_parametrized_function_side_values_at_qps(
581 0 : _local_side_eim_basis_functions[basis_function_index],
582 : elem_id,
583 : side_index,
584 : comp,
585 : values);
586 0 : }
587 :
588 0 : Number RBEIMEvaluation::get_eim_basis_function_node_local_value(unsigned int basis_function_index,
589 : dof_id_type node_id,
590 : unsigned int comp) const
591 : {
592 0 : libmesh_error_msg_if(basis_function_index >= _local_node_eim_basis_functions.size(),
593 : "Invalid basis function index: " << basis_function_index);
594 :
595 0 : return get_parametrized_function_node_local_value(
596 0 : _local_node_eim_basis_functions[basis_function_index],
597 : node_id,
598 0 : comp);
599 : }
600 :
601 0 : Number RBEIMEvaluation::get_eim_basis_function_node_value(unsigned int basis_function_index,
602 : dof_id_type node_id,
603 : unsigned int comp) const
604 : {
605 0 : libmesh_error_msg_if(basis_function_index >= _local_node_eim_basis_functions.size(),
606 : "Invalid basis function index: " << basis_function_index);
607 :
608 0 : return get_parametrized_function_node_value(
609 : comm(),
610 0 : _local_node_eim_basis_functions[basis_function_index],
611 : node_id,
612 0 : comp);
613 : }
614 :
615 0 : Number RBEIMEvaluation::get_eim_basis_function_value(unsigned int basis_function_index,
616 : dof_id_type elem_id,
617 : unsigned int comp,
618 : unsigned int qp) const
619 : {
620 0 : libmesh_error_msg_if(basis_function_index >= _local_eim_basis_functions.size(),
621 : "Invalid basis function index: " << basis_function_index);
622 :
623 0 : return get_parametrized_function_value(
624 : comm(),
625 0 : _local_eim_basis_functions[basis_function_index],
626 : elem_id,
627 : comp,
628 0 : qp);
629 : }
630 :
631 0 : Number RBEIMEvaluation::get_eim_basis_function_side_value(unsigned int basis_function_index,
632 : dof_id_type elem_id,
633 : unsigned int side_index,
634 : unsigned int comp,
635 : unsigned int qp) const
636 : {
637 0 : libmesh_error_msg_if(basis_function_index >= _local_side_eim_basis_functions.size(),
638 : "Invalid side basis function index: " << basis_function_index);
639 :
640 0 : return get_parametrized_function_side_value(
641 : comm(),
642 0 : _local_side_eim_basis_functions[basis_function_index],
643 : elem_id,
644 : side_index,
645 : comp,
646 0 : qp);
647 : }
648 :
649 : const RBEIMEvaluation::QpDataMap &
650 0 : RBEIMEvaluation::get_basis_function(unsigned int i) const
651 : {
652 0 : return _local_eim_basis_functions[i];
653 : }
654 :
655 : const RBEIMEvaluation::SideQpDataMap &
656 0 : RBEIMEvaluation::get_side_basis_function(unsigned int i) const
657 : {
658 0 : return _local_side_eim_basis_functions[i];
659 : }
660 :
661 : const RBEIMEvaluation::NodeDataMap &
662 0 : RBEIMEvaluation::get_node_basis_function(unsigned int i) const
663 : {
664 0 : return _local_node_eim_basis_functions[i];
665 : }
666 :
667 0 : void RBEIMEvaluation::set_rb_eim_solutions(const std::vector<DenseVector<Number>> & rb_eim_solutions)
668 : {
669 0 : _rb_eim_solutions = rb_eim_solutions;
670 0 : }
671 :
672 0 : const std::vector<DenseVector<Number>> & RBEIMEvaluation::get_rb_eim_solutions() const
673 : {
674 0 : return _rb_eim_solutions;
675 : }
676 :
677 0 : std::vector<Number> RBEIMEvaluation::get_rb_eim_solutions_entries(unsigned int index) const
678 : {
679 0 : LOG_SCOPE("get_rb_eim_solutions_entries()", "RBEIMEvaluation");
680 :
681 0 : std::vector<Number> rb_eim_solutions_entries(_rb_eim_solutions.size());
682 0 : for (unsigned int mu_index : index_range(_rb_eim_solutions))
683 : {
684 0 : libmesh_error_msg_if(index >= _rb_eim_solutions[mu_index].size(),
685 : "Error: Requested solution index " << index <<
686 : ", but only have " << _rb_eim_solutions[mu_index].size() << " entries.");
687 0 : rb_eim_solutions_entries[mu_index] = _rb_eim_solutions[mu_index](index);
688 : }
689 :
690 0 : return rb_eim_solutions_entries;
691 : }
692 :
693 0 : const std::vector<DenseVector<Number>> & RBEIMEvaluation::get_eim_solutions_for_training_set() const
694 : {
695 0 : return _eim_solutions_for_training_set;
696 : }
697 :
698 0 : std::vector<DenseVector<Number>> & RBEIMEvaluation::get_eim_solutions_for_training_set()
699 : {
700 0 : return _eim_solutions_for_training_set;
701 : }
702 :
703 0 : const std::vector<std::pair<Real,Real>> & RBEIMEvaluation::get_rb_eim_error_indicators() const
704 : {
705 0 : return _rb_eim_error_indicators;
706 : }
707 :
708 0 : void RBEIMEvaluation::add_interpolation_points_xyz(Point p)
709 : {
710 0 : _vec_eval_input.all_xyz.emplace_back(p);
711 0 : }
712 :
713 0 : void RBEIMEvaluation::add_interpolation_points_comp(unsigned int comp)
714 : {
715 0 : _interpolation_points_comp.emplace_back(comp);
716 0 : }
717 :
718 0 : void RBEIMEvaluation::add_interpolation_points_subdomain_id(subdomain_id_type sbd_id)
719 : {
720 0 : _vec_eval_input.sbd_ids.emplace_back(sbd_id);
721 0 : }
722 :
723 0 : void RBEIMEvaluation::add_interpolation_points_boundary_id(boundary_id_type b_id)
724 : {
725 0 : _vec_eval_input.boundary_ids.emplace_back(b_id);
726 0 : }
727 :
728 0 : void RBEIMEvaluation::add_interpolation_points_xyz_perturbations(const std::vector<Point> & perturbs)
729 : {
730 0 : _vec_eval_input.all_xyz_perturb.emplace_back(perturbs);
731 0 : }
732 :
733 0 : void RBEIMEvaluation::add_interpolation_points_elem_id(dof_id_type elem_id)
734 : {
735 0 : _vec_eval_input.elem_ids.emplace_back(elem_id);
736 0 : }
737 :
738 0 : void RBEIMEvaluation::add_interpolation_points_side_index(unsigned int side_index)
739 : {
740 0 : _vec_eval_input.side_indices.emplace_back(side_index);
741 0 : }
742 :
743 0 : void RBEIMEvaluation::add_interpolation_points_node_id(dof_id_type node_id)
744 : {
745 0 : _vec_eval_input.node_ids.emplace_back(node_id);
746 0 : }
747 :
748 0 : void RBEIMEvaluation::add_interpolation_points_qp(unsigned int qp)
749 : {
750 0 : _vec_eval_input.qps.emplace_back(qp);
751 0 : }
752 :
753 0 : void RBEIMEvaluation::add_interpolation_points_elem_type(ElemType elem_type)
754 : {
755 0 : _vec_eval_input.elem_types.emplace_back(elem_type);
756 0 : }
757 :
758 0 : void RBEIMEvaluation::add_interpolation_points_JxW_all_qp(const std::vector<Real> & JxW_all_qp)
759 : {
760 0 : _vec_eval_input.JxW_all_qp.emplace_back(JxW_all_qp);
761 0 : }
762 :
763 0 : void RBEIMEvaluation::add_interpolation_points_phi_i_all_qp(const std::vector<std::vector<Real>> & phi_i_all_qp)
764 : {
765 0 : _vec_eval_input.phi_i_all_qp.emplace_back(phi_i_all_qp);
766 0 : }
767 :
768 0 : void RBEIMEvaluation::add_interpolation_points_phi_i_qp(const std::vector<Real> & phi_i_qp)
769 : {
770 0 : _vec_eval_input.phi_i_qp.emplace_back(phi_i_qp);
771 0 : }
772 :
773 0 : void RBEIMEvaluation::add_elem_center_dxyzdxi(const Point & dxyzdxi)
774 : {
775 0 : _vec_eval_input.dxyzdxi_elem_center.emplace_back(dxyzdxi);
776 0 : }
777 :
778 0 : void RBEIMEvaluation::add_elem_center_dxyzdeta(const Point & dxyzdeta)
779 : {
780 0 : _vec_eval_input.dxyzdeta_elem_center.emplace_back(dxyzdeta);
781 0 : }
782 :
783 0 : void RBEIMEvaluation::add_interpolation_points_qrule_order(Order qrule_order)
784 : {
785 0 : _vec_eval_input.qrule_orders.emplace_back(qrule_order);
786 0 : }
787 :
788 0 : void RBEIMEvaluation::add_interpolation_points_spatial_indices(const std::vector<unsigned int> & spatial_indices)
789 : {
790 0 : _interpolation_points_spatial_indices.emplace_back(spatial_indices);
791 0 : }
792 :
793 0 : void RBEIMEvaluation::add_elem_id_local_index_map_entry(dof_id_type elem_id, unsigned int local_index)
794 : {
795 : // Try to insert object and return an error if object already inserted as duplicated should not happen.
796 0 : bool insert_succeed = _vec_eval_input.elem_id_to_local_index.insert({elem_id, local_index}).second;
797 0 : libmesh_error_msg_if(!insert_succeed, "Entry already added, duplicate detected.");
798 0 : }
799 :
800 0 : void RBEIMEvaluation::add_rb_property_map_entry(std::string & property_name, std::set<dof_id_type> & entity_ids)
801 : {
802 0 : bool insert_succeed = _vec_eval_input.rb_property_map.insert({property_name, entity_ids}).second;
803 0 : libmesh_error_msg_if(!insert_succeed, "Entry already added, duplicate detected.");
804 0 : }
805 :
806 0 : Point RBEIMEvaluation::get_interpolation_points_xyz(unsigned int index) const
807 : {
808 0 : libmesh_error_msg_if(index >= _vec_eval_input.all_xyz.size(), "Error: Invalid index");
809 :
810 0 : return _vec_eval_input.all_xyz[index];
811 : }
812 :
813 0 : unsigned int RBEIMEvaluation::get_interpolation_points_comp(unsigned int index) const
814 : {
815 0 : libmesh_error_msg_if(index >= _interpolation_points_comp.size(), "Error: Invalid index");
816 :
817 0 : return _interpolation_points_comp[index];
818 : }
819 :
820 0 : subdomain_id_type RBEIMEvaluation::get_interpolation_points_subdomain_id(unsigned int index) const
821 : {
822 0 : libmesh_error_msg_if(index >= _vec_eval_input.sbd_ids.size(), "Error: Invalid index");
823 :
824 0 : return _vec_eval_input.sbd_ids[index];
825 : }
826 :
827 0 : boundary_id_type RBEIMEvaluation::get_interpolation_points_boundary_id(unsigned int index) const
828 : {
829 0 : libmesh_error_msg_if(index >= _vec_eval_input.boundary_ids.size(), "Error: Invalid index");
830 :
831 0 : return _vec_eval_input.boundary_ids[index];
832 : }
833 :
834 0 : const std::vector<Point> & RBEIMEvaluation::get_interpolation_points_xyz_perturbations(unsigned int index) const
835 : {
836 0 : libmesh_error_msg_if(index >= _vec_eval_input.all_xyz_perturb.size(), "Error: Invalid index");
837 :
838 0 : return _vec_eval_input.all_xyz_perturb[index];
839 : }
840 :
841 0 : dof_id_type RBEIMEvaluation::get_interpolation_points_elem_id(unsigned int index) const
842 : {
843 0 : libmesh_error_msg_if(index >= _vec_eval_input.elem_ids.size(), "Error: Invalid index");
844 :
845 0 : return _vec_eval_input.elem_ids[index];
846 : }
847 :
848 0 : unsigned int RBEIMEvaluation::get_interpolation_points_side_index(unsigned int index) const
849 : {
850 0 : libmesh_error_msg_if(index >= _vec_eval_input.side_indices.size(), "Error: Invalid index");
851 :
852 0 : return _vec_eval_input.side_indices[index];
853 : }
854 :
855 0 : dof_id_type RBEIMEvaluation::get_interpolation_points_node_id(unsigned int index) const
856 : {
857 0 : libmesh_error_msg_if(index >= _vec_eval_input.node_ids.size(), "Error: Invalid index");
858 :
859 0 : return _vec_eval_input.node_ids[index];
860 : }
861 :
862 0 : unsigned int RBEIMEvaluation::get_interpolation_points_qp(unsigned int index) const
863 : {
864 0 : libmesh_error_msg_if(index >= _vec_eval_input.qps.size(), "Error: Invalid index");
865 :
866 0 : return _vec_eval_input.qps[index];
867 : }
868 :
869 0 : ElemType RBEIMEvaluation::get_interpolation_points_elem_type(unsigned int index) const
870 : {
871 0 : libmesh_error_msg_if(index >= _vec_eval_input.elem_types.size(), "Error: Invalid index");
872 :
873 0 : return _vec_eval_input.elem_types[index];
874 : }
875 :
876 0 : const std::vector<Real> & RBEIMEvaluation::get_interpolation_points_JxW_all_qp(unsigned int index) const
877 : {
878 0 : libmesh_error_msg_if(index >= _vec_eval_input.JxW_all_qp.size(), "Error: Invalid index");
879 :
880 0 : return _vec_eval_input.JxW_all_qp[index];
881 : }
882 :
883 0 : const std::map<dof_id_type, unsigned int> & RBEIMEvaluation::get_elem_id_to_local_index_map() const
884 : {
885 0 : return _vec_eval_input.elem_id_to_local_index;
886 : }
887 :
888 0 : const std::unordered_map<std::string, std::set<dof_id_type>> & RBEIMEvaluation::get_rb_property_map() const
889 : {
890 0 : return _vec_eval_input.rb_property_map;
891 : }
892 :
893 0 : const std::vector<std::vector<Real>> & RBEIMEvaluation::get_interpolation_points_phi_i_all_qp(unsigned int index) const
894 : {
895 0 : libmesh_error_msg_if(index >= _vec_eval_input.phi_i_all_qp.size(), "Error: Invalid index");
896 :
897 0 : return _vec_eval_input.phi_i_all_qp[index];
898 : }
899 :
900 0 : const std::vector<Real> & RBEIMEvaluation::get_interpolation_points_phi_i_qp(unsigned int index) const
901 : {
902 0 : libmesh_error_msg_if(index >= _vec_eval_input.phi_i_qp.size(), "Error: Invalid index");
903 :
904 0 : return _vec_eval_input.phi_i_qp[index];
905 : }
906 :
907 0 : const Point & RBEIMEvaluation::get_elem_center_dxyzdxi(unsigned int index) const
908 : {
909 0 : libmesh_error_msg_if(index >= _vec_eval_input.dxyzdxi_elem_center.size(), "Error: Invalid index");
910 :
911 0 : return _vec_eval_input.dxyzdxi_elem_center[index];
912 : }
913 :
914 0 : const Point & RBEIMEvaluation::get_elem_center_dxyzdeta(unsigned int index) const
915 : {
916 0 : libmesh_error_msg_if(index >= _vec_eval_input.dxyzdeta_elem_center.size(), "Error: Invalid index");
917 :
918 0 : return _vec_eval_input.dxyzdeta_elem_center[index];
919 : }
920 :
921 0 : Order RBEIMEvaluation::get_interpolation_points_qrule_order(unsigned int index) const
922 : {
923 0 : libmesh_error_msg_if(index >= _vec_eval_input.qrule_orders.size(), "Error: Invalid index");
924 :
925 0 : return _vec_eval_input.qrule_orders[index];
926 : }
927 :
928 0 : const std::vector<unsigned int> & RBEIMEvaluation::get_interpolation_points_spatial_indices(unsigned int index) const
929 : {
930 0 : libmesh_error_msg_if(index >= _interpolation_points_spatial_indices.size(), "Error: Invalid index");
931 :
932 0 : return _interpolation_points_spatial_indices[index];
933 : }
934 :
935 0 : unsigned int RBEIMEvaluation::get_n_interpolation_points_spatial_indices() const
936 : {
937 0 : return _interpolation_points_spatial_indices.size();
938 : }
939 :
940 0 : void RBEIMEvaluation::set_interpolation_matrix_entry(unsigned int i, unsigned int j, Number value)
941 : {
942 0 : libmesh_error_msg_if((i >= _interpolation_matrix.m()) || (j >= _interpolation_matrix.n()),
943 : "Error: Invalid matrix indices");
944 :
945 0 : _interpolation_matrix(i,j) = value;
946 0 : }
947 :
948 0 : const DenseMatrix<Number> & RBEIMEvaluation::get_interpolation_matrix() const
949 : {
950 0 : return _interpolation_matrix;
951 : }
952 :
953 0 : void RBEIMEvaluation::set_preserve_rb_eim_solutions(bool preserve_rb_eim_solutions)
954 : {
955 0 : _preserve_rb_eim_solutions = preserve_rb_eim_solutions;
956 0 : }
957 :
958 0 : bool RBEIMEvaluation::get_preserve_rb_eim_solutions() const
959 : {
960 0 : return _preserve_rb_eim_solutions;
961 : }
962 :
963 0 : void RBEIMEvaluation::add_basis_function(
964 : const QpDataMap & bf)
965 : {
966 0 : _local_eim_basis_functions.emplace_back(bf);
967 0 : }
968 :
969 0 : void RBEIMEvaluation::add_interpolation_data(
970 : Point p,
971 : unsigned int comp,
972 : dof_id_type elem_id,
973 : subdomain_id_type subdomain_id,
974 : unsigned int qp,
975 : const std::vector<Point> & perturbs,
976 : const std::vector<Real> & phi_i_qp,
977 : ElemType elem_type,
978 : const std::vector<Real> & JxW_all_qp,
979 : const std::vector<std::vector<Real>> & phi_i_all_qp,
980 : Order qrule_order,
981 : const Point & dxyzdxi_elem_center,
982 : const Point & dxyzdeta_elem_center)
983 : {
984 0 : _vec_eval_input.all_xyz.emplace_back(p);
985 0 : _interpolation_points_comp.emplace_back(comp);
986 0 : _vec_eval_input.elem_ids.emplace_back(elem_id);
987 0 : _vec_eval_input.sbd_ids.emplace_back(subdomain_id);
988 0 : _vec_eval_input.qps.emplace_back(qp);
989 0 : _vec_eval_input.all_xyz_perturb.emplace_back(perturbs);
990 0 : _vec_eval_input.phi_i_qp.emplace_back(phi_i_qp);
991 0 : _vec_eval_input.elem_types.emplace_back(elem_type);
992 :
993 : // The following quantities are indexed by elem id to prevent duplicated data
994 : // If an entry is already present then we should not need to add that data again as it is element
995 : // based so it should be identical between 2 points if they refer to the same element id.
996 0 : if (_vec_eval_input.elem_id_to_local_index.count(elem_id) == 0)
997 : {
998 0 : unsigned int local_index = _vec_eval_input.JxW_all_qp.size();
999 : // Maybe check that getting local index from a a different source is still ok.
1000 0 : _vec_eval_input.elem_id_to_local_index[elem_id] = local_index;
1001 0 : _vec_eval_input.JxW_all_qp.emplace_back(JxW_all_qp);
1002 0 : _vec_eval_input.phi_i_all_qp.emplace_back(phi_i_all_qp);
1003 0 : _vec_eval_input.qrule_orders.emplace_back(qrule_order);
1004 0 : _vec_eval_input.dxyzdxi_elem_center.emplace_back(dxyzdxi_elem_center);
1005 0 : _vec_eval_input.dxyzdeta_elem_center.emplace_back(dxyzdeta_elem_center);
1006 : }
1007 :
1008 : // add_interpolation_data_to_property_map is a virtual function that aims to add only the property data
1009 : // required by the corresponding rb_parametrized_function.
1010 : // Right now, only elem_ids are supported but if subdomain_ids need to be stored for a property
1011 : // it can be added to the list of arguments and used in the overridden subclass function, the map type
1012 : // might also required an update to support various id types.
1013 0 : get_parametrized_function().add_interpolation_data_to_rb_property_map(this->comm(),
1014 0 : _vec_eval_input.rb_property_map,
1015 0 : elem_id);
1016 0 : }
1017 :
1018 0 : void RBEIMEvaluation::add_side_basis_function(
1019 : const SideQpDataMap & side_bf)
1020 : {
1021 0 : _local_side_eim_basis_functions.emplace_back(side_bf);
1022 0 : }
1023 :
1024 0 : void RBEIMEvaluation::add_side_interpolation_data(
1025 : Point p,
1026 : unsigned int comp,
1027 : dof_id_type elem_id,
1028 : unsigned int side_index,
1029 : subdomain_id_type subdomain_id,
1030 : boundary_id_type boundary_id,
1031 : unsigned int qp,
1032 : const std::vector<Point> & perturbs,
1033 : const std::vector<Real> & phi_i_qp)
1034 : {
1035 0 : _vec_eval_input.all_xyz.emplace_back(p);
1036 0 : _interpolation_points_comp.emplace_back(comp);
1037 0 : _vec_eval_input.elem_ids.emplace_back(elem_id);
1038 0 : _vec_eval_input.side_indices.emplace_back(side_index);
1039 0 : _vec_eval_input.sbd_ids.emplace_back(subdomain_id);
1040 0 : _vec_eval_input.boundary_ids.emplace_back(boundary_id);
1041 0 : _vec_eval_input.qps.emplace_back(qp);
1042 0 : _vec_eval_input.all_xyz_perturb.emplace_back(perturbs);
1043 0 : _vec_eval_input.phi_i_qp.emplace_back(phi_i_qp);
1044 :
1045 : // Add dummy values for the other properties, which are unused in the
1046 : // node case.
1047 0 : _vec_eval_input.elem_types.emplace_back(INVALID_ELEM);
1048 0 : _vec_eval_input.JxW_all_qp.emplace_back();
1049 0 : _vec_eval_input.phi_i_all_qp.emplace_back();
1050 0 : _vec_eval_input.qrule_orders.emplace_back(INVALID_ORDER);
1051 0 : _vec_eval_input.dxyzdxi_elem_center.emplace_back();
1052 0 : _vec_eval_input.dxyzdeta_elem_center.emplace_back();
1053 0 : }
1054 :
1055 0 : void RBEIMEvaluation::add_node_basis_function(
1056 : const NodeDataMap & node_bf)
1057 : {
1058 0 : _local_node_eim_basis_functions.emplace_back(node_bf);
1059 0 : }
1060 :
1061 0 : void RBEIMEvaluation::add_node_interpolation_data(
1062 : Point p,
1063 : unsigned int comp,
1064 : dof_id_type node_id,
1065 : boundary_id_type boundary_id)
1066 : {
1067 0 : _vec_eval_input.all_xyz.emplace_back(p);
1068 0 : _interpolation_points_comp.emplace_back(comp);
1069 0 : _vec_eval_input.node_ids.emplace_back(node_id);
1070 0 : _vec_eval_input.boundary_ids.emplace_back(boundary_id);
1071 :
1072 : // Add dummy values for the other properties, which are unused in the
1073 : // node case.
1074 0 : _vec_eval_input.elem_ids.emplace_back(0);
1075 0 : _vec_eval_input.side_indices.emplace_back(0);
1076 0 : _vec_eval_input.sbd_ids.emplace_back(0);
1077 0 : _vec_eval_input.qps.emplace_back(0);
1078 0 : _vec_eval_input.all_xyz_perturb.emplace_back();
1079 0 : _vec_eval_input.phi_i_qp.emplace_back();
1080 0 : _vec_eval_input.elem_types.emplace_back(INVALID_ELEM);
1081 0 : _vec_eval_input.JxW_all_qp.emplace_back();
1082 0 : _vec_eval_input.phi_i_all_qp.emplace_back();
1083 0 : _vec_eval_input.qrule_orders.emplace_back(INVALID_ORDER);
1084 0 : _vec_eval_input.dxyzdxi_elem_center.emplace_back();
1085 0 : _vec_eval_input.dxyzdeta_elem_center.emplace_back();
1086 0 : }
1087 :
1088 0 : void RBEIMEvaluation::
1089 : write_out_basis_functions(const std::string & directory_name,
1090 : bool write_binary_basis_functions)
1091 : {
1092 0 : LOG_SCOPE("write_out_basis_functions()", "RBEIMEvaluation");
1093 :
1094 0 : if (get_parametrized_function().on_mesh_sides())
1095 0 : write_out_side_basis_functions(directory_name, write_binary_basis_functions);
1096 0 : else if (get_parametrized_function().on_mesh_nodes())
1097 0 : write_out_node_basis_functions(directory_name, write_binary_basis_functions);
1098 : else
1099 0 : write_out_interior_basis_functions(directory_name, write_binary_basis_functions);
1100 0 : }
1101 :
1102 0 : void RBEIMEvaluation::
1103 : write_out_interior_basis_functions(const std::string & directory_name,
1104 : bool write_binary_basis_functions)
1105 : {
1106 0 : LOG_SCOPE("write_out_interior_basis_functions()", "RBEIMEvaluation");
1107 :
1108 : // Quick return if there is no work to do. Note: make sure all procs
1109 : // agree there is no work to do.
1110 0 : bool is_empty = _local_eim_basis_functions.empty();
1111 0 : libmesh_assert(this->comm().verify(is_empty));
1112 :
1113 0 : if (is_empty)
1114 0 : return;
1115 :
1116 : // Gather basis function data from other procs, storing it in
1117 : // _local_eim_basis_functions, so that we can then print everything
1118 : // from processor 0.
1119 0 : this->gather_bfs();
1120 :
1121 : // Write values from processor 0 only.
1122 0 : if (this->processor_id() == 0)
1123 : {
1124 0 : std::vector<unsigned int> n_qp_per_elem;
1125 : auto interior_basis_function_sizes =
1126 0 : get_interior_basis_function_sizes(n_qp_per_elem);
1127 :
1128 : // Make a directory to store all the data files
1129 0 : Utility::mkdir(directory_name.c_str());
1130 :
1131 : // Create filename
1132 0 : std::ostringstream file_name;
1133 0 : const std::string basis_function_suffix = (write_binary_basis_functions ? ".xdr" : ".dat");
1134 0 : file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1135 :
1136 : // Create XDR writer object
1137 0 : Xdr xdr(file_name.str(), write_binary_basis_functions ? ENCODE : WRITE);
1138 :
1139 : // Write number of basis functions to file. Note: the
1140 : // Xdr::data() function takes non-const references, so you can't
1141 : // pass e.g. vec.size() to that interface.
1142 0 : auto n_bf = libmesh_map_find(interior_basis_function_sizes,"n_bf");
1143 0 : xdr.data(n_bf, "# Number of basis functions");
1144 :
1145 : // We assume that each basis function has data for the same
1146 : // number of elements as basis function 0, which is equal to the
1147 : // size of the map.
1148 0 : auto n_elem = libmesh_map_find(interior_basis_function_sizes,"n_elem");
1149 0 : xdr.data(n_elem, "# Number of elements");
1150 :
1151 : // We assume that each element has the same number of variables,
1152 : // and we get the number of vars from the first element of the
1153 : // first basis function.
1154 0 : auto n_vars = libmesh_map_find(interior_basis_function_sizes,"n_vars");
1155 0 : xdr.data(n_vars, "# Number of variables");
1156 :
1157 : // We assume that the list of elements for each basis function
1158 : // is the same as basis function 0. We also assume that all vars
1159 : // have the same number of qps.
1160 0 : xdr.data(n_qp_per_elem, "# Number of QPs per Elem");
1161 :
1162 : // The total amount of qp data for each var is the sum of the
1163 : // entries in the "n_qp_per_elem" array.
1164 0 : auto n_qp_data = libmesh_map_find(interior_basis_function_sizes,"n_qp_data");
1165 :
1166 : // Now we construct a vector for each basis function, for each
1167 : // variable which is ordered according to:
1168 : // [ [qp vals for Elem 0], [qp vals for Elem 1], ... [qp vals for Elem N] ]
1169 : // and write it to file.
1170 0 : for (auto bf_index : index_range(_local_eim_basis_functions))
1171 : {
1172 0 : auto qp_data = get_interior_basis_function_as_vec_helper(n_vars, n_qp_data, bf_index);
1173 :
1174 : // Write all the var values for this bf
1175 0 : for (auto var : index_range(qp_data))
1176 0 : xdr.data_stream(qp_data[var].data(), qp_data[var].size(), /*line_break=*/qp_data[var].size());
1177 0 : }
1178 0 : }
1179 : }
1180 :
1181 0 : std::map<std::string,std::size_t> RBEIMEvaluation::
1182 : get_interior_basis_function_sizes(std::vector<unsigned int> & n_qp_per_elem)
1183 : {
1184 0 : std::map<std::string,std::size_t> interior_basis_function_sizes;
1185 :
1186 : // Write number of basis functions to file. Note: the
1187 : // Xdr::data() function takes non-const references, so you can't
1188 : // pass e.g. vec.size() to that interface.
1189 0 : auto n_bf = _local_eim_basis_functions.size();
1190 0 : interior_basis_function_sizes["n_bf"] = n_bf;
1191 :
1192 : // We assume that each basis function has data for the same
1193 : // number of elements as basis function 0, which is equal to the
1194 : // size of the map.
1195 0 : auto n_elem = _local_eim_basis_functions[0].size();
1196 0 : interior_basis_function_sizes["n_elem"] = n_elem;
1197 :
1198 : // We assume that each element has the same number of variables,
1199 : // and we get the number of vars from the first element of the
1200 : // first basis function.
1201 0 : auto n_vars = _local_eim_basis_functions[0].begin()->second.size();
1202 0 : interior_basis_function_sizes["n_vars"] = n_vars;
1203 :
1204 : // We assume that the list of elements for each basis function
1205 : // is the same as basis function 0. We also assume that all vars
1206 : // have the same number of qps.
1207 0 : n_qp_per_elem.clear();
1208 0 : n_qp_per_elem.reserve(n_elem);
1209 0 : dof_id_type expected_elem_id = 0;
1210 0 : for (const auto & [actual_elem_id, array] : _local_eim_basis_functions[0])
1211 : {
1212 : // Note: Currently we require that the Elems are numbered
1213 : // contiguously from [0..n_elem). This allows us to avoid
1214 : // writing the Elem ids to the Xdr file, but if we need to
1215 : // generalize this assumption later, we can.
1216 0 : libmesh_error_msg_if(actual_elem_id != expected_elem_id++,
1217 : "RBEIMEvaluation currently assumes a contiguous Elem numbering starting from 0.");
1218 :
1219 : // array[n_vars][n_qp] per Elem. We get the number of QPs
1220 : // for variable 0, assuming they are all the same.
1221 0 : n_qp_per_elem.push_back(array[0].size());
1222 : }
1223 :
1224 : // The total amount of qp data for each var is the sum of the
1225 : // entries in the "n_qp_per_elem" array.
1226 : auto n_qp_data =
1227 0 : std::accumulate(n_qp_per_elem.begin(),
1228 : n_qp_per_elem.end(),
1229 : 0u);
1230 0 : interior_basis_function_sizes["n_qp_data"] = n_qp_data;
1231 :
1232 0 : return interior_basis_function_sizes;
1233 : }
1234 :
1235 0 : std::vector<std::vector<Number>> RBEIMEvaluation::
1236 : get_interior_basis_function_as_vec_helper(
1237 : unsigned int n_vars,
1238 : unsigned int n_qp_data,
1239 : unsigned int bf_index)
1240 : {
1241 0 : LOG_SCOPE("get_interior_basis_function_as_vec_helper()", "RBEIMEvaluation");
1242 :
1243 0 : std::vector<std::vector<Number>> qp_data(n_vars);
1244 :
1245 : // Reserve enough capacity in qp_data in order to do the insertions below
1246 : // without further memory allocation.
1247 0 : for (auto var : index_range(qp_data))
1248 0 : qp_data[var].reserve(n_qp_data);
1249 :
1250 : // Now we construct a vector for each basis function, for each
1251 : // variable which is ordered according to:
1252 : // [ [qp vals for Elem 0], [qp vals for Elem 1], ... [qp vals for Elem N] ]
1253 : // and write it to file.
1254 :
1255 0 : libmesh_error_msg_if(bf_index >= _local_eim_basis_functions.size(), "bf_index not valid");
1256 0 : for (const auto & pr : _local_eim_basis_functions[bf_index])
1257 : {
1258 : // array[n_vars][n_qp] per Elem
1259 0 : const auto & array = pr.second;
1260 0 : for (auto var : index_range(array))
1261 : {
1262 : // Insert all qp values for this var
1263 0 : qp_data[var].insert(/*insert at*/qp_data[var].end(),
1264 0 : /*data start*/array[var].begin(),
1265 0 : /*data end*/array[var].end());
1266 : }
1267 : }
1268 :
1269 0 : return qp_data;
1270 0 : }
1271 :
1272 0 : std::vector<std::vector<std::vector<Number>>> RBEIMEvaluation::
1273 : get_interior_basis_functions_as_vecs()
1274 : {
1275 0 : LOG_SCOPE("get_interior_basis_function_as_vec()", "RBEIMEvaluation");
1276 :
1277 0 : std::vector<std::vector<std::vector<Number>>> interior_basis_functions;
1278 :
1279 : // Quick return if there is no work to do. Note: make sure all procs
1280 : // agree there is no work to do.
1281 0 : bool is_empty = _local_eim_basis_functions.empty();
1282 0 : libmesh_assert(this->comm().verify(is_empty));
1283 :
1284 0 : if (is_empty)
1285 0 : return interior_basis_functions;
1286 :
1287 : // Gather basis function data from other procs, storing it in
1288 : // _local_eim_basis_functions, so that we can then print everything
1289 : // from processor 0.
1290 0 : this->gather_bfs();
1291 :
1292 0 : if (this->processor_id() == 0)
1293 : {
1294 0 : std::vector<unsigned int> n_qp_per_elem;
1295 : std::map<std::string,std::size_t> interior_basis_function_sizes =
1296 0 : get_interior_basis_function_sizes(n_qp_per_elem);
1297 :
1298 0 : for (auto bf_index : index_range(_local_eim_basis_functions))
1299 0 : interior_basis_functions.emplace_back(
1300 0 : get_interior_basis_function_as_vec_helper(
1301 0 : libmesh_map_find(interior_basis_function_sizes,"n_vars"),
1302 0 : libmesh_map_find(interior_basis_function_sizes,"n_qp_data"),
1303 0 : bf_index));
1304 : }
1305 :
1306 0 : return interior_basis_functions;
1307 0 : }
1308 :
1309 0 : void RBEIMEvaluation::
1310 : write_out_side_basis_functions(const std::string & directory_name,
1311 : bool write_binary_basis_functions)
1312 : {
1313 0 : LOG_SCOPE("write_out_side_basis_functions()", "RBEIMEvaluation");
1314 :
1315 : // Quick return if there is no work to do. Note: make sure all procs
1316 : // agree there is no work to do.
1317 0 : bool is_empty = _local_side_eim_basis_functions.empty();
1318 0 : libmesh_assert(this->comm().verify(is_empty));
1319 :
1320 0 : if (is_empty)
1321 0 : return;
1322 :
1323 : // Gather basis function data from other procs, storing it in
1324 : // _local_side_eim_basis_functions, so that we can then print everything
1325 : // from processor 0.
1326 0 : this->side_gather_bfs();
1327 :
1328 : // Write values from processor 0 only.
1329 0 : if (this->processor_id() == 0)
1330 : {
1331 : // Make a directory to store all the data files
1332 0 : Utility::mkdir(directory_name.c_str());
1333 :
1334 : // Create filename
1335 0 : std::ostringstream file_name;
1336 0 : const std::string basis_function_suffix = (write_binary_basis_functions ? ".xdr" : ".dat");
1337 0 : file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1338 :
1339 : // Create XDR writer object
1340 0 : Xdr xdr(file_name.str(), write_binary_basis_functions ? ENCODE : WRITE);
1341 :
1342 : // Write number of basis functions to file. Note: the
1343 : // Xdr::data() function takes non-const references, so you can't
1344 : // pass e.g. vec.size() to that interface.
1345 0 : auto n_bf = _local_side_eim_basis_functions.size();
1346 0 : xdr.data(n_bf, "# Number of basis functions");
1347 :
1348 : // We assume that each basis function has data for the same
1349 : // number of (elem,side) pairs as basis function 0, which is equal to the
1350 : // size of the map.
1351 0 : auto n_elem = _local_side_eim_basis_functions[0].size();
1352 0 : xdr.data(n_elem, "# Number of (elem,side) pairs");
1353 :
1354 : // We assume that each element has the same number of variables,
1355 : // and we get the number of vars from the first element of the
1356 : // first basis function.
1357 0 : auto n_vars = _local_side_eim_basis_functions[0].begin()->second.size();
1358 0 : xdr.data(n_vars, "# Number of variables");
1359 :
1360 : // We write out the following arrays:
1361 : // - element IDs
1362 : // - side indices
1363 : // - n_qp_per_elem_side
1364 0 : std::vector<unsigned int> n_qp_per_elem_side;
1365 0 : std::vector<unsigned int> elem_ids;
1366 0 : std::vector<unsigned int> side_indices;
1367 0 : elem_ids.reserve(n_elem);
1368 0 : side_indices.reserve(n_elem);
1369 0 : n_qp_per_elem_side.reserve(n_elem);
1370 0 : for (const auto & [elem_side_pair, array] : _local_side_eim_basis_functions[0])
1371 : {
1372 0 : elem_ids.push_back(elem_side_pair.first);
1373 0 : side_indices.push_back(elem_side_pair.second);
1374 :
1375 : // array[n_vars][n_qp] per Elem. We get the number of QPs
1376 : // for variable 0, assuming they are all the same.
1377 0 : n_qp_per_elem_side.push_back(array[0].size());
1378 : }
1379 0 : xdr.data(elem_ids, "# Elem IDs");
1380 0 : xdr.data(side_indices, "# Side indices");
1381 0 : xdr.data(n_qp_per_elem_side, "# Number of QPs per Elem");
1382 :
1383 : // The total amount of qp data for each var is the sum of the
1384 : // entries in the "n_qp_per_elem" array.
1385 : auto n_qp_data =
1386 0 : std::accumulate(n_qp_per_elem_side.begin(),
1387 : n_qp_per_elem_side.end(),
1388 : 0u);
1389 :
1390 : // Reserve space to store contiguous vectors of qp data for each var
1391 0 : std::vector<std::vector<Number>> qp_data(n_vars);
1392 0 : for (auto var : index_range(qp_data))
1393 0 : qp_data[var].reserve(n_qp_data);
1394 :
1395 : // Now we construct a vector for each basis function, for each
1396 : // variable which is ordered according to:
1397 : // [ [qp vals for Elem 0], [qp vals for Elem 1], ... [qp vals for Elem N] ]
1398 : // and write it to file.
1399 0 : for (auto bf : index_range(_local_side_eim_basis_functions))
1400 : {
1401 : // Clear any data from previous bf
1402 0 : for (auto var : index_range(qp_data))
1403 0 : qp_data[var].clear();
1404 :
1405 0 : for (const auto & pr : _local_side_eim_basis_functions[bf])
1406 : {
1407 : // array[n_vars][n_qp] per Elem
1408 0 : const auto & array = pr.second;
1409 0 : for (auto var : index_range(array))
1410 : {
1411 : // Insert all qp values for this var
1412 0 : qp_data[var].insert(/*insert at*/qp_data[var].end(),
1413 0 : /*data start*/array[var].begin(),
1414 0 : /*data end*/array[var].end());
1415 : }
1416 : }
1417 :
1418 : // Write all the var values for this bf
1419 0 : for (auto var : index_range(qp_data))
1420 0 : xdr.data_stream(qp_data[var].data(), qp_data[var].size(), /*line_break=*/qp_data[var].size());
1421 : }
1422 0 : }
1423 : }
1424 :
1425 0 : void RBEIMEvaluation::
1426 : write_out_node_basis_functions(const std::string & directory_name,
1427 : bool write_binary_basis_functions)
1428 : {
1429 0 : LOG_SCOPE("write_out_node_basis_functions()", "RBEIMEvaluation");
1430 :
1431 : // Quick return if there is no work to do. Note: make sure all procs
1432 : // agree there is no work to do.
1433 0 : bool is_empty = _local_node_eim_basis_functions.empty();
1434 0 : libmesh_assert(this->comm().verify(is_empty));
1435 :
1436 0 : if (is_empty)
1437 0 : return;
1438 :
1439 : // Gather basis function data from other procs, storing it in
1440 : // _local_node_eim_basis_functions, so that we can then print everything
1441 : // from processor 0.
1442 0 : this->node_gather_bfs();
1443 :
1444 : // Write values from processor 0 only.
1445 0 : if (this->processor_id() == 0)
1446 : {
1447 : // Make a directory to store all the data files
1448 0 : Utility::mkdir(directory_name.c_str());
1449 :
1450 : // Create filename
1451 0 : std::ostringstream file_name;
1452 0 : const std::string basis_function_suffix = (write_binary_basis_functions ? ".xdr" : ".dat");
1453 0 : file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1454 :
1455 : // Create XDR writer object
1456 0 : Xdr xdr(file_name.str(), write_binary_basis_functions ? ENCODE : WRITE);
1457 :
1458 : // Write number of basis functions to file. Note: the
1459 : // Xdr::data() function takes non-const references, so you can't
1460 : // pass e.g. vec.size() to that interface.
1461 0 : auto n_bf = _local_node_eim_basis_functions.size();
1462 0 : xdr.data(n_bf, "# Number of basis functions");
1463 :
1464 : // We assume that each basis function has data for the same
1465 : // number of elements as basis function 0, which is equal to the
1466 : // size of the map.
1467 0 : auto n_node = _local_node_eim_basis_functions[0].size();
1468 0 : xdr.data(n_node, "# Number of nodes");
1469 :
1470 : // We assume that each element has the same number of variables,
1471 : // and we get the number of vars from the first element of the
1472 : // first basis function.
1473 0 : auto n_vars = _local_node_eim_basis_functions[0].begin()->second.size();
1474 0 : xdr.data(n_vars, "# Number of variables");
1475 :
1476 : // We write out the following arrays:
1477 : // - node IDs
1478 0 : std::vector<unsigned int> node_ids;
1479 0 : node_ids.reserve(n_node);
1480 0 : for (const auto & pr : _local_node_eim_basis_functions[0])
1481 : {
1482 0 : node_ids.push_back(pr.first);
1483 : }
1484 0 : xdr.data(node_ids, "# Node IDs");
1485 :
1486 : // Now we construct a vector for each basis function, for each
1487 : // variable which is ordered according to:
1488 : // [ [val for Node 0], [val for Node 1], ... [val for Node N] ]
1489 : // and write it to file.
1490 :
1491 0 : std::vector<std::vector<Number>> var_data(n_vars);
1492 0 : for (unsigned int var=0; var<n_vars; var++)
1493 0 : var_data[var].resize(n_node);
1494 :
1495 0 : for (auto bf : index_range(_local_node_eim_basis_functions))
1496 : {
1497 0 : unsigned int node_counter = 0;
1498 0 : for (const auto & pr : _local_node_eim_basis_functions[bf])
1499 : {
1500 : // array[n_vars] per Node
1501 0 : const auto & array = pr.second;
1502 0 : for (auto var : index_range(array))
1503 : {
1504 : // Based on the error check above, we know that node_id is numbered
1505 : // contiguously from [0..nodes], so we can use it as the vector
1506 : // index here.
1507 0 : var_data[var][node_counter] = array[var];
1508 : }
1509 :
1510 0 : node_counter++;
1511 : }
1512 :
1513 : // Write all the var values for this bf
1514 0 : for (auto var : index_range(var_data))
1515 0 : xdr.data_stream(var_data[var].data(), var_data[var].size(), /*line_break=*/var_data[var].size());
1516 : }
1517 0 : }
1518 : }
1519 :
1520 0 : void RBEIMEvaluation::
1521 : read_in_basis_functions(const System & sys,
1522 : const std::string & directory_name,
1523 : bool read_binary_basis_functions)
1524 : {
1525 0 : LOG_SCOPE("read_in_basis_functions()", "RBEIMEvaluation");
1526 :
1527 : // Return early without reading in anything if there are no basis functions
1528 0 : if (get_n_basis_functions() == 0)
1529 0 : return;
1530 :
1531 0 : if (get_parametrized_function().on_mesh_sides())
1532 0 : read_in_side_basis_functions(sys, directory_name, read_binary_basis_functions);
1533 0 : else if (get_parametrized_function().on_mesh_nodes())
1534 0 : read_in_node_basis_functions(sys, directory_name, read_binary_basis_functions);
1535 : else
1536 0 : read_in_interior_basis_functions(sys, directory_name, read_binary_basis_functions);
1537 : }
1538 :
1539 0 : void RBEIMEvaluation::
1540 : read_in_interior_basis_functions(const System & sys,
1541 : const std::string & directory_name,
1542 : bool read_binary_basis_functions)
1543 : {
1544 0 : LOG_SCOPE("read_in_interior_basis_functions()", "RBEIMEvaluation");
1545 :
1546 : // Read values on processor 0 only.
1547 0 : if (sys.comm().rank() == 0)
1548 : {
1549 : // Create filename
1550 0 : std::ostringstream file_name;
1551 0 : const std::string basis_function_suffix = (read_binary_basis_functions ? ".xdr" : ".dat");
1552 0 : file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1553 :
1554 : // Create XDR reader object
1555 0 : Xdr xdr(file_name.str(), read_binary_basis_functions ? DECODE : READ);
1556 :
1557 : // Read in the number of basis functions. The comment parameter
1558 : // is ignored when reading.
1559 : std::size_t n_bf;
1560 0 : xdr.data(n_bf);
1561 :
1562 : // Read in the number of elements
1563 : std::size_t n_elem;
1564 0 : xdr.data(n_elem);
1565 :
1566 : // Read in the number of variables.
1567 : std::size_t n_vars;
1568 0 : xdr.data(n_vars);
1569 :
1570 : // Read in vector containing the number of QPs per elem. We can
1571 : // create this vector with the required size or let it be read
1572 : // from the file and sized for us.
1573 0 : std::vector<unsigned int> n_qp_per_elem(n_elem);
1574 0 : xdr.data(n_qp_per_elem);
1575 :
1576 : // The total amount of qp data for each var is the sum of the
1577 : // entries in the "n_qp_per_elem" array.
1578 : auto n_qp_data =
1579 0 : std::accumulate(n_qp_per_elem.begin(),
1580 : n_qp_per_elem.end(),
1581 : 0u);
1582 :
1583 : // Allocate space to store all required basis functions,
1584 : // clearing any data that may have been there previously.
1585 : //
1586 : // TODO: Do we need to also write out/read in Elem ids?
1587 : // Or can we assume they will always be contiguously
1588 : // numbered (at least on proc 0)?
1589 0 : _local_eim_basis_functions.clear();
1590 0 : _local_eim_basis_functions.resize(n_bf);
1591 0 : for (auto i : index_range(_local_eim_basis_functions))
1592 0 : for (std::size_t elem_id=0; elem_id<n_elem; ++elem_id)
1593 : {
1594 0 : auto & array = _local_eim_basis_functions[i][elem_id];
1595 0 : array.resize(n_vars);
1596 : }
1597 :
1598 : // Allocate temporary storage for one var's worth of qp data.
1599 0 : std::vector<Number> qp_data;
1600 :
1601 : // Read in data for each basis function
1602 0 : for (auto i : index_range(_local_eim_basis_functions))
1603 : {
1604 : // Reference to the data map for the current basis function.
1605 0 : auto & bf_map = _local_eim_basis_functions[i];
1606 :
1607 0 : for (std::size_t var=0; var<n_vars; ++var)
1608 : {
1609 0 : qp_data.clear();
1610 0 : qp_data.resize(n_qp_data);
1611 :
1612 : // Read data using data_stream() since that is
1613 : // (currently) how we write it out. The "line_break"
1614 : // parameter of data_stream() is ignored while reading.
1615 0 : xdr.data_stream(qp_data.data(), qp_data.size());
1616 :
1617 : // Iterate over the qp_data vector, filling in the
1618 : // "small" vectors for each Elem.
1619 0 : auto cursor = qp_data.begin();
1620 0 : for (std::size_t elem_id=0; elem_id<n_elem; ++elem_id)
1621 : {
1622 : // Get reference to the [n_vars][n_qp] array for
1623 : // this Elem. We assign() into the vector of
1624 : // quadrature point values, which allocates space if
1625 : // it doesn't already exist.
1626 0 : auto & array = bf_map[elem_id];
1627 0 : array[var].assign(cursor, cursor + n_qp_per_elem[elem_id]);
1628 0 : std::advance(cursor, n_qp_per_elem[elem_id]);
1629 : }
1630 : } // end for (var)
1631 : } // end for (i)
1632 0 : } // end if processor 0
1633 :
1634 : // Distribute the basis function information to the processors that require it
1635 0 : this->distribute_bfs(sys);
1636 0 : }
1637 :
1638 0 : void RBEIMEvaluation::
1639 : read_in_side_basis_functions(const System & sys,
1640 : const std::string & directory_name,
1641 : bool read_binary_basis_functions)
1642 : {
1643 0 : LOG_SCOPE("read_in_basis_functions()", "RBEIMEvaluation");
1644 :
1645 : // Read values on processor 0 only.
1646 0 : if (sys.comm().rank() == 0)
1647 : {
1648 : // Create filename
1649 0 : std::ostringstream file_name;
1650 0 : const std::string basis_function_suffix = (read_binary_basis_functions ? ".xdr" : ".dat");
1651 0 : file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1652 :
1653 : // Create XDR reader object
1654 0 : Xdr xdr(file_name.str(), read_binary_basis_functions ? DECODE : READ);
1655 :
1656 : // Read in the number of basis functions. The comment parameter
1657 : // is ignored when reading.
1658 : std::size_t n_bf;
1659 0 : xdr.data(n_bf);
1660 :
1661 : // Read in the number of elements
1662 : std::size_t n_elem_side;
1663 0 : xdr.data(n_elem_side);
1664 :
1665 : // Read in the number of variables.
1666 : std::size_t n_vars;
1667 0 : xdr.data(n_vars);
1668 :
1669 0 : std::vector<unsigned int> elem_ids(n_elem_side);
1670 0 : xdr.data(elem_ids);
1671 0 : std::vector<unsigned int> side_indices(n_elem_side);
1672 0 : xdr.data(side_indices);
1673 :
1674 : // Read in vector containing the number of QPs per elem. We can
1675 : // create this vector with the required size or let it be read
1676 : // from the file and sized for us.
1677 0 : std::vector<unsigned int> n_qp_per_elem_side(n_elem_side);
1678 0 : xdr.data(n_qp_per_elem_side);
1679 :
1680 : // The total amount of qp data for each var is the sum of the
1681 : // entries in the "n_qp_per_elem" array.
1682 : auto n_qp_data =
1683 0 : std::accumulate(n_qp_per_elem_side.begin(),
1684 : n_qp_per_elem_side.end(),
1685 : 0u);
1686 :
1687 : // Allocate space to store all required basis functions,
1688 : // clearing any data that may have been there previously.
1689 0 : _local_side_eim_basis_functions.clear();
1690 0 : _local_side_eim_basis_functions.resize(n_bf);
1691 0 : for (auto i : index_range(_local_side_eim_basis_functions))
1692 0 : for (std::size_t elem_side_idx=0; elem_side_idx<n_elem_side; ++elem_side_idx)
1693 : {
1694 0 : unsigned int elem_id = elem_ids[elem_side_idx];
1695 0 : unsigned int side_index = side_indices[elem_side_idx];
1696 0 : auto elem_side_pair = std::make_pair(elem_id, side_index);
1697 :
1698 0 : auto & array = _local_side_eim_basis_functions[i][elem_side_pair];
1699 0 : array.resize(n_vars);
1700 : }
1701 :
1702 : // Allocate temporary storage for one var's worth of qp data.
1703 0 : std::vector<Number> qp_data;
1704 :
1705 : // Read in data for each basis function
1706 0 : for (auto i : index_range(_local_side_eim_basis_functions))
1707 : {
1708 : // Reference to the data map for the current basis function.
1709 0 : auto & bf_map = _local_side_eim_basis_functions[i];
1710 :
1711 0 : for (std::size_t var=0; var<n_vars; ++var)
1712 : {
1713 0 : qp_data.clear();
1714 0 : qp_data.resize(n_qp_data);
1715 :
1716 : // Read data using data_stream() since that is
1717 : // (currently) how we write it out. The "line_break"
1718 : // parameter of data_stream() is ignored while reading.
1719 0 : xdr.data_stream(qp_data.data(), qp_data.size());
1720 :
1721 : // Iterate over the qp_data vector, filling in the
1722 : // "small" vectors for each Elem.
1723 0 : auto cursor = qp_data.begin();
1724 0 : for (std::size_t elem_side_idx=0; elem_side_idx<n_elem_side; ++elem_side_idx)
1725 : {
1726 0 : unsigned int elem_id = elem_ids[elem_side_idx];
1727 0 : unsigned int side_index = side_indices[elem_side_idx];
1728 0 : auto elem_side_pair = std::make_pair(elem_id, side_index);
1729 :
1730 : // Get reference to the [n_vars][n_qp] array for
1731 : // this Elem. We assign() into the vector of
1732 : // quadrature point values, which allocates space if
1733 : // it doesn't already exist.
1734 0 : auto & array = bf_map[elem_side_pair];
1735 0 : array[var].assign(cursor, cursor + n_qp_per_elem_side[elem_side_idx]);
1736 0 : std::advance(cursor, n_qp_per_elem_side[elem_side_idx]);
1737 : }
1738 : } // end for (var)
1739 : } // end for (i)
1740 0 : } // end if processor 0
1741 :
1742 : // Distribute the basis function information to the processors that require it
1743 0 : this->side_distribute_bfs(sys);
1744 0 : }
1745 :
1746 0 : void RBEIMEvaluation::
1747 : read_in_node_basis_functions(const System & sys,
1748 : const std::string & directory_name,
1749 : bool read_binary_basis_functions)
1750 : {
1751 0 : LOG_SCOPE("read_in_node_basis_functions()", "RBEIMEvaluation");
1752 :
1753 : // Read values on processor 0 only.
1754 0 : if (sys.comm().rank() == 0)
1755 : {
1756 : // Create filename
1757 0 : std::ostringstream file_name;
1758 0 : const std::string basis_function_suffix = (read_binary_basis_functions ? ".xdr" : ".dat");
1759 0 : file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1760 :
1761 : // Create XDR reader object
1762 0 : Xdr xdr(file_name.str(), read_binary_basis_functions ? DECODE : READ);
1763 :
1764 : // Read in the number of basis functions. The comment parameter
1765 : // is ignored when reading.
1766 : std::size_t n_bf;
1767 0 : xdr.data(n_bf);
1768 :
1769 : // Read in the number of nodes
1770 : std::size_t n_node;
1771 0 : xdr.data(n_node);
1772 :
1773 : // Read in the number of variables.
1774 : std::size_t n_vars;
1775 0 : xdr.data(n_vars);
1776 :
1777 0 : std::vector<unsigned int> node_ids(n_node);
1778 0 : xdr.data(node_ids);
1779 :
1780 : // Allocate space to store all required basis functions,
1781 : // clearing any data that may have been there previously.
1782 : //
1783 : // TODO: Do we need to also write out/read in Node ids?
1784 : // Or can we assume they will always be contiguously
1785 : // numbered (at least on proc 0)?
1786 0 : _local_node_eim_basis_functions.clear();
1787 0 : _local_node_eim_basis_functions.resize(n_bf);
1788 0 : for (auto i : index_range(_local_node_eim_basis_functions))
1789 0 : for (auto node_id : node_ids)
1790 : {
1791 0 : auto & array = _local_node_eim_basis_functions[i][node_id];
1792 0 : array.resize(n_vars);
1793 : }
1794 :
1795 : // Read data into node_value from xdr
1796 0 : std::vector<Number> node_value;
1797 :
1798 : // Read in data for each basis function
1799 0 : for (auto i : index_range(_local_node_eim_basis_functions))
1800 : {
1801 : // Reference to the data map for the current basis function.
1802 0 : auto & bf_map = _local_node_eim_basis_functions[i];
1803 :
1804 0 : for (std::size_t var=0; var<n_vars; ++var)
1805 : {
1806 0 : node_value.clear();
1807 0 : node_value.resize(n_node);
1808 :
1809 : // Read data using data_stream() since that is
1810 : // (currently) how we write it out. The "line_break"
1811 : // parameter of data_stream() is ignored while reading.
1812 0 : xdr.data_stream(node_value.data(), node_value.size());
1813 :
1814 0 : for (unsigned int node_counter=0; node_counter<n_node; node_counter++)
1815 : {
1816 0 : auto & array = bf_map[node_ids[node_counter]];
1817 0 : array[var] = node_value[node_counter];
1818 : }
1819 : } // end for (var)
1820 : } // end for (i)
1821 0 : } // end if processor 0
1822 :
1823 : // Distribute the basis function information to the processors that require it
1824 0 : this->node_distribute_bfs(sys);
1825 0 : }
1826 :
1827 0 : void RBEIMEvaluation::print_local_eim_basis_functions() const
1828 : {
1829 0 : for (auto bf : index_range(_local_eim_basis_functions))
1830 : {
1831 0 : libMesh::out << "Interior basis function " << bf << std::endl;
1832 0 : for (const auto & [elem_id, array] : _local_eim_basis_functions[bf])
1833 : {
1834 0 : libMesh::out << "Elem " << elem_id << std::endl;
1835 0 : for (auto var : index_range(array))
1836 : {
1837 0 : libMesh::out << "Variable " << var << std::endl;
1838 0 : for (auto qp : index_range(array[var]))
1839 0 : libMesh::out << array[var][qp] << " ";
1840 0 : libMesh::out << std::endl;
1841 : }
1842 : }
1843 : }
1844 :
1845 0 : for (auto bf : index_range(_local_side_eim_basis_functions))
1846 : {
1847 0 : libMesh::out << "Side basis function " << bf << std::endl;
1848 0 : for (const auto & [pr, array] : _local_side_eim_basis_functions[bf])
1849 : {
1850 0 : const auto & elem_id = pr.first;
1851 0 : const auto & side_index = pr.second;
1852 0 : libMesh::out << "Elem " << elem_id << ", Side " << side_index << std::endl;
1853 0 : for (auto var : index_range(array))
1854 : {
1855 0 : libMesh::out << "Variable " << var << std::endl;
1856 0 : for (auto qp : index_range(array[var]))
1857 0 : libMesh::out << array[var][qp] << " ";
1858 0 : libMesh::out << std::endl;
1859 : }
1860 : }
1861 : }
1862 :
1863 0 : for (auto bf : index_range(_local_node_eim_basis_functions))
1864 : {
1865 0 : libMesh::out << "Node basis function " << bf << std::endl;
1866 0 : for (const auto & [node_id, array] : _local_node_eim_basis_functions[bf])
1867 : {
1868 0 : libMesh::out << "Node " << node_id << std::endl;
1869 0 : for (auto var : index_range(array))
1870 : {
1871 0 : libMesh::out << "Variable " << var << ": " << array[var] << std::endl;
1872 : }
1873 0 : libMesh::out << std::endl;
1874 : }
1875 : }
1876 0 : }
1877 :
1878 0 : void RBEIMEvaluation::gather_bfs()
1879 : {
1880 : // We need to gather _local_eim_basis_functions data from other
1881 : // procs for printing.
1882 : //
1883 : // Ideally, this could be accomplished by simply calling:
1884 : // this->comm().gather(/*root_id=*/0, _local_eim_basis_functions);
1885 : //
1886 : // but the data structure seems to be too complicated for this to
1887 : // work automatically. (I get some error about the function called
1888 : // being "private within this context".) Therefore, we have to
1889 : // gather the information manually.
1890 :
1891 : // So we can avoid calling this many times below
1892 0 : auto n_procs = this->n_processors();
1893 :
1894 : // In serial there's nothing to gather
1895 0 : if (n_procs == 1)
1896 0 : return;
1897 :
1898 : // Current assumption is that the number of basis functions stored on
1899 : // each processor is the same, the only thing that differs is the number
1900 : // of elements, so make sure that is the case now.
1901 0 : auto n_bf = _local_eim_basis_functions.size();
1902 0 : libmesh_assert(this->comm().verify(n_bf));
1903 :
1904 : // This function should never be called if there are no basis
1905 : // functions, so if it was, something went wrong.
1906 0 : libmesh_error_msg_if(!n_bf, "RBEIMEvaluation::gather_bfs() should not be called with 0 basis functions.");
1907 :
1908 : // The number of variables should be the same on all processors
1909 : // and we can get this from _local_eim_basis_functions. However,
1910 : // it may be that some processors have no local elements, so on
1911 : // those processors we cannot look up the size from
1912 : // _local_eim_basis_functions. As a result we use comm().max(n_vars)
1913 : // to make sure all processors agree on the final value.
1914 : std::size_t n_vars =
1915 0 : _local_eim_basis_functions[0].empty() ? 0 : _local_eim_basis_functions[0].begin()->second.size();
1916 0 : this->comm().max(n_vars);
1917 :
1918 : // Gather list of Elem ids stored on each processor to proc 0. We
1919 : // use basis function 0 as an example and assume all the basis
1920 : // functions are distributed similarly.
1921 0 : std::vector<dof_id_type> elem_ids;
1922 0 : elem_ids.reserve(_local_eim_basis_functions[0].size());
1923 0 : for (const auto & pr : _local_eim_basis_functions[0])
1924 0 : elem_ids.push_back(pr.first);
1925 0 : this->comm().gather(/*root_id=*/0, elem_ids);
1926 :
1927 : // Store the number of qps per Elem on this processor. Again, use
1928 : // basis function 0 (and variable 0) to get this information, then
1929 : // apply it to all basis functions.
1930 0 : std::vector<unsigned int> n_qp_per_elem;
1931 0 : n_qp_per_elem.reserve(_local_eim_basis_functions[0].size());
1932 0 : for (const auto & pr : _local_eim_basis_functions[0])
1933 : {
1934 : // array[n_vars][n_qp] per Elem. We get the number of QPs
1935 : // for variable 0, assuming they are all the same.
1936 0 : const auto & array = pr.second;
1937 0 : n_qp_per_elem.push_back(array[0].size());
1938 : }
1939 :
1940 : // Before gathering, compute the total amount of local qp data for
1941 : // each var, which is the sum of the entries in the "n_qp_per_elem" array.
1942 : // This will be used to reserve space in a vector below.
1943 : auto n_local_qp_data =
1944 0 : std::accumulate(n_qp_per_elem.begin(),
1945 : n_qp_per_elem.end(),
1946 : 0u);
1947 :
1948 : // Gather the number of qps per Elem for each processor onto processor 0.
1949 0 : this->comm().gather(/*root_id=*/0, n_qp_per_elem);
1950 :
1951 : // Sanity check: On processor 0, this checks that we have gathered the same number
1952 : // of elem ids and qp counts.
1953 0 : libmesh_error_msg_if(elem_ids.size() != n_qp_per_elem.size(),
1954 : "Must gather same number of Elem ids as qps per Elem.");
1955 :
1956 : // Reserve space to store contiguous vectors of qp data for each var
1957 0 : std::vector<std::vector<Number>> gathered_qp_data(n_vars);
1958 0 : for (auto var : index_range(gathered_qp_data))
1959 0 : gathered_qp_data[var].reserve(n_local_qp_data);
1960 :
1961 : // Now we construct a vector for each basis function, for each
1962 : // variable, which is ordered according to:
1963 : // [ [qp vals for Elem 0], [qp vals for Elem 1], ... [qp vals for Elem N] ]
1964 : // and gather it to processor 0.
1965 0 : for (auto bf : index_range(_local_eim_basis_functions))
1966 : {
1967 : // Clear any data from previous bf
1968 0 : for (auto var : index_range(gathered_qp_data))
1969 0 : gathered_qp_data[var].clear();
1970 :
1971 0 : for (const auto & pr : _local_eim_basis_functions[bf])
1972 : {
1973 : // array[n_vars][n_qp] per Elem
1974 0 : const auto & array = pr.second;
1975 0 : for (auto var : index_range(array))
1976 : {
1977 : // Insert all qp values for this var
1978 0 : gathered_qp_data[var].insert(/*insert at*/gathered_qp_data[var].end(),
1979 0 : /*data start*/array[var].begin(),
1980 0 : /*data end*/array[var].end());
1981 : }
1982 : }
1983 :
1984 : // Reference to the data map for the current basis function.
1985 0 : auto & bf_map = _local_eim_basis_functions[bf];
1986 :
1987 0 : for (auto var : index_range(gathered_qp_data))
1988 : {
1989 : // For each var, gather gathered_qp_data[var] onto processor
1990 : // 0. There apparently is not a gather overload for
1991 : // vector-of-vectors...
1992 0 : this->comm().gather(/*root_id=*/0, gathered_qp_data[var]);
1993 :
1994 : // On processor 0, iterate over the gathered_qp_data[var]
1995 : // vector we just gathered, filling in the "small" vectors
1996 : // for each Elem. Note: here we ignore the fact that we
1997 : // already have the data on processor 0 and just overwrite
1998 : // it, this makes the indexing logic a bit simpler.
1999 0 : if (this->processor_id() == 0)
2000 : {
2001 0 : auto cursor = gathered_qp_data[var].begin();
2002 0 : for (auto i : index_range(elem_ids))
2003 : {
2004 0 : auto elem_id = elem_ids[i];
2005 0 : auto n_qp_this_elem = n_qp_per_elem[i];
2006 :
2007 : // Get reference to the [n_vars][n_qp] array for
2008 : // this Elem. We assign() into the vector of
2009 : // quadrature point values, which allocates space if
2010 : // it doesn't already exist.
2011 0 : auto & array = bf_map[elem_id];
2012 :
2013 : // Possibly allocate space if this is data for a new
2014 : // element we haven't seen before.
2015 0 : if (array.empty())
2016 0 : array.resize(n_vars);
2017 :
2018 0 : array[var].assign(cursor, cursor + n_qp_this_elem);
2019 0 : std::advance(cursor, n_qp_this_elem);
2020 : }
2021 : }
2022 : }
2023 : } // end loop over basis functions
2024 0 : }
2025 :
2026 0 : void RBEIMEvaluation::side_gather_bfs()
2027 : {
2028 : // We need to gather _local_side_eim_basis_functions data from other
2029 : // procs for printing.
2030 : //
2031 : // Ideally, this could be accomplished by simply calling:
2032 : // this->comm().gather(/*root_id=*/0, _local_side_eim_basis_functions);
2033 : //
2034 : // but the data structure seems to be too complicated for this to
2035 : // work automatically. (I get some error about the function called
2036 : // being "private within this context".) Therefore, we have to
2037 : // gather the information manually.
2038 :
2039 : // So we can avoid calling this many times below
2040 0 : auto n_procs = this->n_processors();
2041 :
2042 : // In serial there's nothing to gather
2043 0 : if (n_procs == 1)
2044 0 : return;
2045 :
2046 : // Current assumption is that the number of basis functions stored on
2047 : // each processor is the same, the only thing that differs is the number
2048 : // of elements, so make sure that is the case now.
2049 0 : auto n_bf = _local_side_eim_basis_functions.size();
2050 0 : libmesh_assert(this->comm().verify(n_bf));
2051 :
2052 : // This function should never be called if there are no basis
2053 : // functions, so if it was, something went wrong.
2054 0 : libmesh_error_msg_if(!n_bf, "SideRBEIMEvaluation::gather_bfs() should not be called with 0 basis functions.");
2055 :
2056 : // The number of variables should be the same on all processors
2057 : // and we can get this from _local_side_eim_basis_functions. However,
2058 : // it may be that some processors have no local elements, so on
2059 : // those processors we cannot look up the size from
2060 : // _local_side_eim_basis_functions. As a result we use comm().max(n_vars)
2061 : // to make sure all processors agree on the final value.
2062 : std::size_t n_vars =
2063 0 : _local_side_eim_basis_functions[0].empty() ? 0 : _local_side_eim_basis_functions[0].begin()->second.size();
2064 0 : this->comm().max(n_vars);
2065 :
2066 : // Gather list of (elem,side) pairs stored on each processor to proc 0. We
2067 : // use basis function 0 as an example and assume all the basis
2068 : // functions are distributed similarly.
2069 0 : std::vector<std::pair<dof_id_type,unsigned int>> elem_side_pairs;
2070 0 : elem_side_pairs.reserve(_local_side_eim_basis_functions[0].size());
2071 0 : for (const auto & pr : _local_side_eim_basis_functions[0])
2072 0 : elem_side_pairs.push_back(pr.first);
2073 0 : this->comm().gather(/*root_id=*/0, elem_side_pairs);
2074 :
2075 : // Store the number of qps per Elem on this processor. Again, use
2076 : // basis function 0 (and variable 0) to get this information, then
2077 : // apply it to all basis functions.
2078 0 : std::vector<unsigned int> n_qp_per_elem_side;
2079 0 : n_qp_per_elem_side.reserve(_local_side_eim_basis_functions[0].size());
2080 0 : for (const auto & pr : _local_side_eim_basis_functions[0])
2081 : {
2082 : // array[n_vars][n_qp] per (elem,side). We get the number of QPs
2083 : // for variable 0, assuming they are all the same.
2084 0 : const auto & array = pr.second;
2085 0 : n_qp_per_elem_side.push_back(array[0].size());
2086 : }
2087 :
2088 : // Before gathering, compute the total amount of local qp data for
2089 : // each var, which is the sum of the entries in the "n_qp_per_elem_side" array.
2090 : // This will be used to reserve space in a vector below.
2091 : auto n_local_qp_data =
2092 0 : std::accumulate(n_qp_per_elem_side.begin(),
2093 : n_qp_per_elem_side.end(),
2094 : 0u);
2095 :
2096 : // Gather the number of qps per Elem for each processor onto processor 0.
2097 0 : this->comm().gather(/*root_id=*/0, n_qp_per_elem_side);
2098 :
2099 : // Sanity check: On processor 0, this checks that we have gathered the same number
2100 : // of (elem,side) pairs and qp counts.
2101 0 : libmesh_error_msg_if(elem_side_pairs.size() != n_qp_per_elem_side.size(),
2102 : "Must gather same number of Elem ids as qps per Elem.");
2103 :
2104 : // Reserve space to store contiguous vectors of qp data for each var
2105 0 : std::vector<std::vector<Number>> gathered_qp_data(n_vars);
2106 0 : for (auto var : index_range(gathered_qp_data))
2107 0 : gathered_qp_data[var].reserve(n_local_qp_data);
2108 :
2109 : // Now we construct a vector for each basis function, for each
2110 : // variable, which is ordered according to:
2111 : // [ [qp vals for Elem 0], [qp vals for Elem 1], ... [qp vals for Elem N] ]
2112 : // and gather it to processor 0.
2113 0 : for (auto bf : index_range(_local_side_eim_basis_functions))
2114 : {
2115 : // Clear any data from previous bf
2116 0 : for (auto var : index_range(gathered_qp_data))
2117 0 : gathered_qp_data[var].clear();
2118 :
2119 0 : for (const auto & pr : _local_side_eim_basis_functions[bf])
2120 : {
2121 : // array[n_vars][n_qp] per (elem,side) pair
2122 0 : const auto & array = pr.second;
2123 0 : for (auto var : index_range(array))
2124 : {
2125 : // Insert all qp values for this var
2126 0 : gathered_qp_data[var].insert(/*insert at*/gathered_qp_data[var].end(),
2127 0 : /*data start*/array[var].begin(),
2128 0 : /*data end*/array[var].end());
2129 : }
2130 : }
2131 :
2132 : // Reference to the data map for the current basis function.
2133 0 : auto & bf_map = _local_side_eim_basis_functions[bf];
2134 :
2135 0 : for (auto var : index_range(gathered_qp_data))
2136 : {
2137 : // For each var, gather gathered_qp_data[var] onto processor
2138 : // 0. There apparently is not a gather overload for
2139 : // vector-of-vectors...
2140 0 : this->comm().gather(/*root_id=*/0, gathered_qp_data[var]);
2141 :
2142 : // On processor 0, iterate over the gathered_qp_data[var]
2143 : // vector we just gathered, filling in the "small" vectors
2144 : // for each Elem. Note: here we ignore the fact that we
2145 : // already have the data on processor 0 and just overwrite
2146 : // it, this makes the indexing logic a bit simpler.
2147 0 : if (this->processor_id() == 0)
2148 : {
2149 0 : auto cursor = gathered_qp_data[var].begin();
2150 0 : for (auto i : index_range(elem_side_pairs))
2151 : {
2152 0 : auto elem_side_pair = elem_side_pairs[i];
2153 0 : auto n_qp_this_elem_side = n_qp_per_elem_side[i];
2154 :
2155 : // Get reference to the [n_vars][n_qp] array for
2156 : // this Elem. We assign() into the vector of
2157 : // quadrature point values, which allocates space if
2158 : // it doesn't already exist.
2159 0 : auto & array = bf_map[elem_side_pair];
2160 :
2161 : // Possibly allocate space if this is data for a new
2162 : // element we haven't seen before.
2163 0 : if (array.empty())
2164 0 : array.resize(n_vars);
2165 :
2166 0 : array[var].assign(cursor, cursor + n_qp_this_elem_side);
2167 0 : std::advance(cursor, n_qp_this_elem_side);
2168 : }
2169 : }
2170 : }
2171 : } // end loop over basis functions
2172 0 : }
2173 :
2174 0 : void RBEIMEvaluation::node_gather_bfs()
2175 : {
2176 : // We need to gather _local_node_eim_basis_functions data from other
2177 : // procs for printing.
2178 : //
2179 : // Ideally, this could be accomplished by simply calling:
2180 : // this->comm().gather(/*root_id=*/0, _local_node_eim_basis_functions);
2181 : //
2182 : // but the data structure seems to be too complicated for this to
2183 : // work automatically. (I get some error about the function called
2184 : // being "private within this context".) Therefore, we have to
2185 : // gather the information manually.
2186 :
2187 : // So we can avoid calling this many times below
2188 0 : auto n_procs = this->n_processors();
2189 :
2190 : // In serial there's nothing to gather
2191 0 : if (n_procs == 1)
2192 0 : return;
2193 :
2194 : // Current assumption is that the number of basis functions stored on
2195 : // each processor is the same, the only thing that differs is the number
2196 : // of elements, so make sure that is the case now.
2197 0 : auto n_bf = _local_node_eim_basis_functions.size();
2198 0 : libmesh_assert(this->comm().verify(n_bf));
2199 :
2200 : // This function should never be called if there are no basis
2201 : // functions, so if it was, something went wrong.
2202 0 : libmesh_error_msg_if(!n_bf, "RBEIMEvaluation::gather_bfs() should not be called with 0 basis functions.");
2203 :
2204 : // The number of variables should be the same on all processors
2205 : // and we can get this from _local_eim_basis_functions. However,
2206 : // it may be that some processors have no local elements, so on
2207 : // those processors we cannot look up the size from
2208 : // _local_eim_basis_functions. As a result we use comm().max(n_vars)
2209 : // to make sure all processors agree on the final value.
2210 : std::size_t n_vars =
2211 0 : _local_node_eim_basis_functions[0].empty() ? 0 : _local_node_eim_basis_functions[0].begin()->second.size();
2212 0 : this->comm().max(n_vars);
2213 :
2214 : // Gather list of Node ids stored on each processor to proc 0. We
2215 : // use basis function 0 as an example and assume all the basis
2216 : // functions are distributed similarly.
2217 0 : unsigned int n_local_nodes = _local_node_eim_basis_functions[0].size();
2218 0 : std::vector<dof_id_type> node_ids;
2219 0 : node_ids.reserve(n_local_nodes);
2220 0 : for (const auto & pr : _local_node_eim_basis_functions[0])
2221 0 : node_ids.push_back(pr.first);
2222 0 : this->comm().gather(/*root_id=*/0, node_ids);
2223 :
2224 : // Now we construct a vector for each basis function, for each
2225 : // variable, which is ordered according to:
2226 : // [ [val for Node 0], [val for Node 1], ... [val for Node N] ]
2227 : // and gather it to processor 0.
2228 0 : std::vector<std::vector<Number>> gathered_node_data(n_vars);
2229 0 : for (auto bf : index_range(_local_node_eim_basis_functions))
2230 : {
2231 : // Clear any data from previous bf
2232 0 : for (auto var : index_range(gathered_node_data))
2233 : {
2234 0 : gathered_node_data[var].clear();
2235 0 : gathered_node_data[var].resize(n_local_nodes);
2236 : }
2237 :
2238 0 : unsigned int local_node_idx = 0;
2239 0 : for (const auto & pr : _local_node_eim_basis_functions[bf])
2240 : {
2241 : // array[n_vars] per Node
2242 0 : const auto & array = pr.second;
2243 0 : for (auto var : index_range(array))
2244 : {
2245 0 : gathered_node_data[var][local_node_idx] = array[var];
2246 : }
2247 :
2248 0 : local_node_idx++;
2249 : }
2250 :
2251 : // Reference to the data map for the current basis function.
2252 0 : auto & bf_map = _local_node_eim_basis_functions[bf];
2253 :
2254 0 : for (auto var : index_range(gathered_node_data))
2255 : {
2256 : // For each var, gather gathered_qp_data[var] onto processor
2257 : // 0. There apparently is not a gather overload for
2258 : // vector-of-vectors...
2259 0 : this->comm().gather(/*root_id=*/0, gathered_node_data[var]);
2260 :
2261 : // On processor 0, iterate over the gathered_qp_data[var]
2262 : // vector we just gathered, filling in the "small" vectors
2263 : // for each Elem. Note: here we ignore the fact that we
2264 : // already have the data on processor 0 and just overwrite
2265 : // it, this makes the indexing logic a bit simpler.
2266 0 : if (this->processor_id() == 0)
2267 : {
2268 0 : auto cursor = gathered_node_data[var].begin();
2269 0 : for (auto i : index_range(node_ids))
2270 : {
2271 0 : auto node_id = node_ids[i];
2272 :
2273 : // Get reference to the [n_vars] array for
2274 : // this Node. We assign() into the vector of
2275 : // node values, which allocates space if
2276 : // it doesn't already exist.
2277 0 : auto & array = bf_map[node_id];
2278 :
2279 : // Possibly allocate space if this is data for a new
2280 : // node we haven't seen before.
2281 0 : if (array.empty())
2282 0 : array.resize(n_vars);
2283 :
2284 : // There is only one value per variable per node, so
2285 : // we set the value by de-referencing cursor, and
2286 : // then advance the cursor by 1.
2287 0 : array[var] = *cursor;
2288 0 : std::advance(cursor, 1);
2289 : }
2290 : }
2291 : }
2292 : } // end loop over basis functions
2293 0 : }
2294 :
2295 :
2296 :
2297 0 : void RBEIMEvaluation::distribute_bfs(const System & sys)
2298 : {
2299 : // So we can avoid calling these many times below
2300 0 : auto n_procs = sys.comm().size();
2301 0 : auto rank = sys.comm().rank();
2302 :
2303 : // In serial there's nothing to distribute
2304 0 : if (n_procs == 1)
2305 0 : return;
2306 :
2307 : // Broadcast the number of basis functions from proc 0. After
2308 : // distributing, all procs should have the same number of basis
2309 : // functions.
2310 0 : auto n_bf = _local_eim_basis_functions.size();
2311 0 : sys.comm().broadcast(n_bf);
2312 :
2313 : // Allocate enough space to store n_bf basis functions on non-zero ranks
2314 0 : if (rank != 0)
2315 0 : _local_eim_basis_functions.resize(n_bf);
2316 :
2317 : // Broadcast the number of variables from proc 0. After
2318 : // distributing, all procs should have the same number of variables.
2319 0 : auto n_vars = _local_eim_basis_functions[0].begin()->second.size();
2320 0 : sys.comm().broadcast(n_vars);
2321 :
2322 : // Construct lists of elem ids owned by different processors
2323 0 : const MeshBase & mesh = sys.get_mesh();
2324 :
2325 0 : std::vector<dof_id_type> gathered_local_elem_ids;
2326 0 : gathered_local_elem_ids.reserve(mesh.n_elem());
2327 0 : for (const auto & elem : mesh.active_local_element_ptr_range())
2328 0 : gathered_local_elem_ids.push_back(elem->id());
2329 :
2330 : // I _think_ the local elem ids are likely to already be sorted in
2331 : // ascending order, since that is how they are stored on the Mesh,
2332 : // but we can always just guarantee this to be on the safe side as
2333 : // well.
2334 0 : std::sort(gathered_local_elem_ids.begin(), gathered_local_elem_ids.end());
2335 :
2336 : // Gather the number of local elems from all procs to proc 0
2337 0 : auto n_local_elems = gathered_local_elem_ids.size();
2338 0 : std::vector<std::size_t> gathered_n_local_elems = {n_local_elems};
2339 0 : sys.comm().gather(/*root_id=*/0, gathered_n_local_elems);
2340 :
2341 : // Gather the elem ids owned by each processor onto processor 0.
2342 0 : sys.comm().gather(/*root_id=*/0, gathered_local_elem_ids);
2343 :
2344 : // Construct vectors of "start" and "one-past-the-end" indices into
2345 : // the gathered_local_elem_ids vector for each proc. Only valid on
2346 : // processor 0.
2347 0 : std::vector<std::size_t> start_elem_ids_index, end_elem_ids_index;
2348 :
2349 0 : if (rank == 0)
2350 : {
2351 0 : start_elem_ids_index.resize(n_procs);
2352 0 : start_elem_ids_index[0] = 0;
2353 0 : for (processor_id_type p=1; p<n_procs; ++p)
2354 0 : start_elem_ids_index[p] = start_elem_ids_index[p-1] + gathered_n_local_elems[p-1];
2355 :
2356 0 : end_elem_ids_index.resize(n_procs);
2357 0 : end_elem_ids_index[n_procs - 1] = gathered_local_elem_ids.size();
2358 0 : for (processor_id_type p=0; p<n_procs - 1; ++p)
2359 0 : end_elem_ids_index[p] = start_elem_ids_index[p+1];
2360 : }
2361 :
2362 : // On processor 0, using basis function 0 and variable 0, prepare a
2363 : // vector with the number of qps per Elem. Then scatter this vector
2364 : // out to the processors that require it. The order of this vector
2365 : // matches the gathered_local_elem_ids ordering. The counts will be
2366 : // gathered_n_local_elems, since there will be one qp count per Elem.
2367 0 : std::vector<unsigned int> n_qp_per_elem_data;
2368 :
2369 : // On rank 0, the "counts" vector holds the number of floating point values that
2370 : // are to be scattered to each proc. It is only required on proc 0.
2371 0 : std::vector<int> counts;
2372 :
2373 0 : if (rank == 0)
2374 : {
2375 0 : n_qp_per_elem_data.reserve(gathered_local_elem_ids.size());
2376 0 : counts.resize(n_procs);
2377 :
2378 0 : auto & bf_map = _local_eim_basis_functions[0];
2379 :
2380 0 : for (processor_id_type p=0; p<n_procs; ++p)
2381 : {
2382 0 : for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2383 : {
2384 0 : auto elem_id = gathered_local_elem_ids[e];
2385 :
2386 : // Get reference to array[n_vars][n_qp] for current Elem.
2387 : // Throws an error if the required elem_id is not found.
2388 0 : const auto & array = libmesh_map_find(bf_map, elem_id);
2389 :
2390 0 : auto n_qps = array[0].size();
2391 :
2392 : // We use var==0 to set the number of qps for all vars
2393 0 : n_qp_per_elem_data.push_back(n_qps);
2394 :
2395 : // Accumulate the count for this proc
2396 0 : counts[p] += n_qps;
2397 : } // end for (e)
2398 : } // end for proc_id
2399 : } // if (rank == 0)
2400 :
2401 : // Now scatter the n_qp_per_elem_data to all procs (must call the
2402 : // scatter on all procs, it is a collective).
2403 : {
2404 0 : std::vector<unsigned int> recv;
2405 0 : std::vector<int> tmp(gathered_n_local_elems.begin(), gathered_n_local_elems.end());
2406 0 : sys.comm().scatter(n_qp_per_elem_data, tmp, recv, /*root_id=*/0);
2407 :
2408 : // Now swap n_qp_per_elem_data and recv. All processors now have a
2409 : // vector of length n_local_elems containing the number of
2410 : // quadarature points per Elem.
2411 0 : n_qp_per_elem_data.swap(recv);
2412 : }
2413 :
2414 : // For each basis function and each variable, build a vector
2415 : // of qp data in the Elem ordering given by the
2416 : // gathered_local_elem_ids, then call
2417 : //
2418 : // sys.comm().scatter(data, counts, recv, /*root_id=*/0);
2419 0 : std::vector<std::vector<Number>> qp_data(n_vars);
2420 0 : if (rank == 0)
2421 : {
2422 : // The total amount of qp data is given by summing the entries
2423 : // of the "counts" vector.
2424 : auto n_qp_data =
2425 0 : std::accumulate(counts.begin(), counts.end(), 0u);
2426 :
2427 : // On processor 0, reserve enough space to hold all the qp
2428 : // data for a single basis function for each var.
2429 0 : for (auto var : index_range(qp_data))
2430 0 : qp_data[var].reserve(n_qp_data);
2431 : }
2432 :
2433 : // The recv_qp_data vector will be used on the receiving end of all
2434 : // the scatters below.
2435 0 : std::vector<Number> recv_qp_data;
2436 :
2437 : // Loop from 0..n_bf on _all_ procs, since the scatters inside this
2438 : // loop are collective.
2439 0 : for (auto bf : make_range(n_bf))
2440 : {
2441 : // Prepare data for scattering (only on proc 0)
2442 0 : if (rank == 0)
2443 : {
2444 : // Reference to the data map for the current basis function.
2445 0 : auto & bf_map = _local_eim_basis_functions[bf];
2446 :
2447 : // Clear any data from previous bf
2448 0 : for (auto var : index_range(qp_data))
2449 0 : qp_data[var].clear();
2450 :
2451 0 : for (processor_id_type p=0; p<n_procs; ++p)
2452 : {
2453 0 : for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2454 : {
2455 0 : auto elem_id = gathered_local_elem_ids[e];
2456 :
2457 : // Get reference to array[n_vars][n_qp] for current Elem.
2458 : // Throws an error if the required elem_id is not found.
2459 0 : const auto & array = libmesh_map_find(bf_map, elem_id);
2460 :
2461 0 : for (auto var : index_range(array))
2462 : {
2463 : // Insert all qp values for this var
2464 0 : qp_data[var].insert(/*insert at*/qp_data[var].end(),
2465 0 : /*data start*/array[var].begin(),
2466 0 : /*data end*/array[var].end());
2467 : } // end for (var)
2468 : } // end for (e)
2469 : } // end for proc_id
2470 : } // end if rank==0
2471 :
2472 : // Perform the scatters (all procs)
2473 0 : for (auto var : make_range(n_vars))
2474 : {
2475 : // Do the scatter for the current var
2476 0 : sys.comm().scatter(qp_data[var], counts, recv_qp_data, /*root_id=*/0);
2477 :
2478 0 : if (rank != 0)
2479 : {
2480 : // Store the scattered data we received in _local_eim_basis_functions[bf]
2481 0 : auto & bf_map = _local_eim_basis_functions[bf];
2482 0 : auto cursor = recv_qp_data.begin();
2483 :
2484 0 : for (auto i : index_range(gathered_local_elem_ids))
2485 : {
2486 0 : auto elem_id = gathered_local_elem_ids[i];
2487 0 : auto n_qp_this_elem = n_qp_per_elem_data[i];
2488 0 : auto & array = bf_map[elem_id];
2489 :
2490 : // Create space to store the data if it doesn't already exist.
2491 0 : if (array.empty())
2492 0 : array.resize(n_vars);
2493 :
2494 0 : array[var].assign(cursor, cursor + n_qp_this_elem);
2495 0 : std::advance(cursor, n_qp_this_elem);
2496 : }
2497 : } // if (rank != 0)
2498 : } // end for (var)
2499 : } // end for (bf)
2500 :
2501 : // Now that the scattering is done, delete non-local Elem
2502 : // information from processor 0's _local_eim_basis_functions data
2503 : // structure.
2504 0 : if (rank == 0)
2505 : {
2506 0 : for (processor_id_type p=1; p<n_procs; ++p)
2507 : {
2508 0 : for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2509 : {
2510 0 : auto elem_id = gathered_local_elem_ids[e];
2511 :
2512 : // Delete this Elem's information from every basis function.
2513 0 : for (auto & bf_map : _local_eim_basis_functions)
2514 0 : bf_map.erase(elem_id);
2515 : } // end for (e)
2516 : } // end for proc_id
2517 : } // if (rank == 0)
2518 0 : }
2519 :
2520 0 : void RBEIMEvaluation::side_distribute_bfs(const System & sys)
2521 : {
2522 : // So we can avoid calling these many times below
2523 0 : auto n_procs = sys.comm().size();
2524 0 : auto rank = sys.comm().rank();
2525 :
2526 : // In serial there's nothing to distribute
2527 0 : if (n_procs == 1)
2528 0 : return;
2529 :
2530 : // Broadcast the number of basis functions from proc 0. After
2531 : // distributing, all procs should have the same number of basis
2532 : // functions.
2533 0 : auto n_bf = _local_side_eim_basis_functions.size();
2534 0 : sys.comm().broadcast(n_bf);
2535 :
2536 : // Allocate enough space to store n_bf basis functions on non-zero ranks
2537 0 : if (rank != 0)
2538 0 : _local_side_eim_basis_functions.resize(n_bf);
2539 :
2540 : // Broadcast the number of variables from proc 0. After
2541 : // distributing, all procs should have the same number of variables.
2542 0 : auto n_vars = _local_side_eim_basis_functions[0].begin()->second.size();
2543 0 : sys.comm().broadcast(n_vars);
2544 :
2545 : const std::set<boundary_id_type> & parametrized_function_boundary_ids =
2546 0 : get_parametrized_function().get_parametrized_function_boundary_ids();
2547 :
2548 : // Construct lists of elem ids owned by different processors
2549 0 : const MeshBase & mesh = sys.get_mesh();
2550 :
2551 : // BoundaryInfo and related data structures
2552 0 : const auto & binfo = mesh.get_boundary_info();
2553 0 : std::vector<boundary_id_type> side_boundary_ids;
2554 :
2555 0 : std::vector<dof_id_type> gathered_local_elem_ids;
2556 0 : std::vector<dof_id_type> gathered_local_side_indices;
2557 0 : for (const auto & elem : mesh.active_local_element_ptr_range())
2558 : {
2559 0 : for (unsigned int side = 0; side != elem->n_sides(); ++side)
2560 : {
2561 : // skip non-boundary elements
2562 0 : if (!elem->neighbor_ptr(side))
2563 : {
2564 0 : binfo.boundary_ids(elem, side, side_boundary_ids);
2565 :
2566 0 : bool has_side_boundary_id = false;
2567 0 : for (boundary_id_type side_boundary_id : side_boundary_ids)
2568 0 : if (parametrized_function_boundary_ids.count(side_boundary_id))
2569 : {
2570 0 : has_side_boundary_id = true;
2571 0 : break;
2572 : }
2573 :
2574 0 : if (has_side_boundary_id)
2575 : {
2576 0 : gathered_local_elem_ids.push_back(elem->id());
2577 0 : gathered_local_side_indices.push_back(side);
2578 : }
2579 : }
2580 : }
2581 :
2582 : // In the case of 2D elements, we also check the shellfaces
2583 0 : if (elem->dim() == 2)
2584 0 : for (unsigned int shellface_index=0; shellface_index<2; shellface_index++)
2585 : {
2586 0 : binfo.shellface_boundary_ids(elem, shellface_index, side_boundary_ids);
2587 :
2588 0 : bool has_side_boundary_id = false;
2589 0 : for (boundary_id_type side_boundary_id : side_boundary_ids)
2590 0 : if (parametrized_function_boundary_ids.count(side_boundary_id))
2591 : {
2592 0 : has_side_boundary_id = true;
2593 0 : break;
2594 : }
2595 :
2596 0 : if (has_side_boundary_id)
2597 : {
2598 : // We use shellface_index as the side_index since shellface boundary conditions
2599 : // are stored separately from side boundary conditions in BoundaryInfo.
2600 0 : gathered_local_elem_ids.push_back(elem->id());
2601 0 : gathered_local_side_indices.push_back(shellface_index);
2602 : }
2603 : }
2604 0 : }
2605 :
2606 : // Gather the number of local elems from all procs to proc 0
2607 0 : auto n_local_elems = gathered_local_elem_ids.size();
2608 0 : std::vector<std::size_t> gathered_n_local_elems = {n_local_elems};
2609 0 : sys.comm().gather(/*root_id=*/0, gathered_n_local_elems);
2610 :
2611 : // Gather the (elem,side) owned by each processor onto processor 0.
2612 0 : sys.comm().gather(/*root_id=*/0, gathered_local_elem_ids);
2613 0 : sys.comm().gather(/*root_id=*/0, gathered_local_side_indices);
2614 :
2615 : // Construct vectors of "start" and "one-past-the-end" indices into
2616 : // the gathered_local_elem_ids vector for each proc. Only valid on
2617 : // processor 0.
2618 0 : std::vector<std::size_t> start_elem_ids_index, end_elem_ids_index;
2619 :
2620 0 : if (rank == 0)
2621 : {
2622 0 : start_elem_ids_index.resize(n_procs);
2623 0 : start_elem_ids_index[0] = 0;
2624 0 : for (processor_id_type p=1; p<n_procs; ++p)
2625 0 : start_elem_ids_index[p] = start_elem_ids_index[p-1] + gathered_n_local_elems[p-1];
2626 :
2627 0 : end_elem_ids_index.resize(n_procs);
2628 0 : end_elem_ids_index[n_procs - 1] = gathered_local_elem_ids.size();
2629 0 : for (processor_id_type p=0; p<n_procs - 1; ++p)
2630 0 : end_elem_ids_index[p] = start_elem_ids_index[p+1];
2631 : }
2632 :
2633 : // On processor 0, using basis function 0 and variable 0, prepare a
2634 : // vector with the number of qps per Elem. Then scatter this vector
2635 : // out to the processors that require it. The order of this vector
2636 : // matches the gathered_local_elem_ids ordering. The counts will be
2637 : // gathered_n_local_elems, since there will be one qp count per Elem.
2638 0 : std::vector<unsigned int> n_qp_per_elem_data;
2639 :
2640 : // On rank 0, the "counts" vector holds the number of floating point values that
2641 : // are to be scattered to each proc. It is only required on proc 0.
2642 0 : std::vector<int> counts;
2643 :
2644 0 : if (rank == 0)
2645 : {
2646 0 : n_qp_per_elem_data.reserve(gathered_local_elem_ids.size());
2647 0 : counts.resize(n_procs);
2648 :
2649 0 : auto & bf_map = _local_side_eim_basis_functions[0];
2650 :
2651 0 : for (processor_id_type p=0; p<n_procs; ++p)
2652 : {
2653 0 : for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2654 : {
2655 0 : auto elem_id = gathered_local_elem_ids[e];
2656 0 : auto side_index = gathered_local_side_indices[e];
2657 :
2658 : // Get reference to array[n_vars][n_qp] for current Elem.
2659 : // Throws an error if the required elem_id is not found.
2660 0 : const auto & array = libmesh_map_find(bf_map, std::make_pair(elem_id, side_index));
2661 :
2662 0 : auto n_qps = array[0].size();
2663 :
2664 : // We use var==0 to set the number of qps for all vars
2665 0 : n_qp_per_elem_data.push_back(n_qps);
2666 :
2667 : // Accumulate the count for this proc
2668 0 : counts[p] += n_qps;
2669 : } // end for (e)
2670 : } // end for proc_id
2671 : } // if (rank == 0)
2672 :
2673 : // Now scatter the n_qp_per_elem_data to all procs (must call the
2674 : // scatter on all procs, it is a collective).
2675 : {
2676 0 : std::vector<unsigned int> recv;
2677 0 : std::vector<int> tmp(gathered_n_local_elems.begin(), gathered_n_local_elems.end());
2678 0 : sys.comm().scatter(n_qp_per_elem_data, tmp, recv, /*root_id=*/0);
2679 :
2680 : // Now swap n_qp_per_elem_data and recv. All processors now have a
2681 : // vector of length n_local_elems containing the number of
2682 : // quadarature points per Elem.
2683 0 : n_qp_per_elem_data.swap(recv);
2684 : }
2685 :
2686 : // For each basis function and each variable, build a vector
2687 : // of qp data in the Elem ordering given by the
2688 : // gathered_local_elem_ids, then call
2689 : //
2690 : // sys.comm().scatter(data, counts, recv, /*root_id=*/0);
2691 0 : std::vector<std::vector<Number>> qp_data(n_vars);
2692 0 : if (rank == 0)
2693 : {
2694 : // The total amount of qp data is given by summing the entries
2695 : // of the "counts" vector.
2696 : auto n_qp_data =
2697 0 : std::accumulate(counts.begin(), counts.end(), 0u);
2698 :
2699 : // On processor 0, reserve enough space to hold all the qp
2700 : // data for a single basis function for each var.
2701 0 : for (auto var : index_range(qp_data))
2702 0 : qp_data[var].reserve(n_qp_data);
2703 : }
2704 :
2705 : // The recv_qp_data vector will be used on the receiving end of all
2706 : // the scatters below.
2707 0 : std::vector<Number> recv_qp_data;
2708 :
2709 : // Loop from 0..n_bf on _all_ procs, since the scatters inside this
2710 : // loop are collective.
2711 0 : for (auto bf : make_range(n_bf))
2712 : {
2713 : // Prepare data for scattering (only on proc 0)
2714 0 : if (rank == 0)
2715 : {
2716 : // Reference to the data map for the current basis function.
2717 0 : auto & bf_map = _local_side_eim_basis_functions[bf];
2718 :
2719 : // Clear any data from previous bf
2720 0 : for (auto var : index_range(qp_data))
2721 0 : qp_data[var].clear();
2722 :
2723 0 : for (processor_id_type p=0; p<n_procs; ++p)
2724 : {
2725 0 : for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2726 : {
2727 0 : auto elem_id = gathered_local_elem_ids[e];
2728 0 : auto side_index = gathered_local_side_indices[e];
2729 :
2730 : // Get reference to array[n_vars][n_qp] for current Elem.
2731 : // Throws an error if the required (elem,side) is not found.
2732 0 : const auto & array = libmesh_map_find(bf_map, std::make_pair(elem_id, side_index));
2733 :
2734 0 : for (auto var : index_range(array))
2735 : {
2736 : // Insert all qp values for this var
2737 0 : qp_data[var].insert(/*insert at*/qp_data[var].end(),
2738 0 : /*data start*/array[var].begin(),
2739 0 : /*data end*/array[var].end());
2740 : } // end for (var)
2741 : } // end for (e)
2742 : } // end for proc_id
2743 : } // end if rank==0
2744 :
2745 : // Perform the scatters (all procs)
2746 0 : for (auto var : make_range(n_vars))
2747 : {
2748 : // Do the scatter for the current var
2749 0 : sys.comm().scatter(qp_data[var], counts, recv_qp_data, /*root_id=*/0);
2750 :
2751 0 : if (rank != 0)
2752 : {
2753 : // Store the scattered data we received in _local_side_eim_basis_functions[bf]
2754 0 : auto & bf_map = _local_side_eim_basis_functions[bf];
2755 0 : auto cursor = recv_qp_data.begin();
2756 :
2757 0 : for (auto i : index_range(gathered_local_elem_ids))
2758 : {
2759 0 : auto elem_id = gathered_local_elem_ids[i];
2760 0 : auto side_index = gathered_local_side_indices[i];
2761 0 : auto n_qp_this_elem = n_qp_per_elem_data[i];
2762 0 : auto & array = bf_map[std::make_pair(elem_id, side_index)];
2763 :
2764 : // Create space to store the data if it doesn't already exist.
2765 0 : if (array.empty())
2766 0 : array.resize(n_vars);
2767 :
2768 0 : array[var].assign(cursor, cursor + n_qp_this_elem);
2769 0 : std::advance(cursor, n_qp_this_elem);
2770 : }
2771 : } // if (rank != 0)
2772 : } // end for (var)
2773 : } // end for (bf)
2774 :
2775 : // Now that the scattering is done, delete non-local Elem
2776 : // information from processor 0's _local_side_eim_basis_functions data
2777 : // structure.
2778 0 : if (rank == 0)
2779 : {
2780 0 : for (processor_id_type p=1; p<n_procs; ++p)
2781 : {
2782 0 : for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2783 : {
2784 0 : auto elem_id = gathered_local_elem_ids[e];
2785 0 : auto side_index = gathered_local_side_indices[e];
2786 :
2787 : // Delete this Elem's information from every basis function.
2788 0 : for (auto & bf_map : _local_side_eim_basis_functions)
2789 0 : bf_map.erase(std::make_pair(elem_id, side_index));
2790 : } // end for (e)
2791 : } // end for proc_id
2792 : } // if (rank == 0)
2793 0 : }
2794 :
2795 0 : void RBEIMEvaluation::node_distribute_bfs(const System & sys)
2796 : {
2797 : // So we can avoid calling these many times below
2798 0 : auto n_procs = sys.comm().size();
2799 0 : auto rank = sys.comm().rank();
2800 :
2801 : // In serial there's nothing to distribute
2802 0 : if (n_procs == 1)
2803 0 : return;
2804 :
2805 : // Broadcast the number of basis functions from proc 0. After
2806 : // distributing, all procs should have the same number of basis
2807 : // functions.
2808 0 : auto n_bf = _local_node_eim_basis_functions.size();
2809 0 : sys.comm().broadcast(n_bf);
2810 :
2811 : // Allocate enough space to store n_bf basis functions on non-zero ranks
2812 0 : if (rank != 0)
2813 0 : _local_node_eim_basis_functions.resize(n_bf);
2814 :
2815 : // Broadcast the number of variables from proc 0. After
2816 : // distributing, all procs should have the same number of variables.
2817 0 : auto n_vars = _local_node_eim_basis_functions[0].begin()->second.size();
2818 0 : sys.comm().broadcast(n_vars);
2819 :
2820 : // Construct lists of elem ids owned by different processors
2821 0 : const MeshBase & mesh = sys.get_mesh();
2822 :
2823 0 : std::vector<dof_id_type> gathered_local_node_ids;
2824 : {
2825 : const std::set<boundary_id_type> & parametrized_function_boundary_ids =
2826 0 : get_parametrized_function().get_parametrized_function_boundary_ids();
2827 :
2828 0 : const auto & binfo = mesh.get_boundary_info();
2829 :
2830 : // Make a set with all the nodes that have nodesets. Use
2831 : // a set so that we don't have any duplicate entries. We
2832 : // deal with duplicate entries below by getting all boundary
2833 : // IDs on each node.
2834 0 : std::set<dof_id_type> nodes_with_nodesets;
2835 0 : for (const auto & t : binfo.build_node_list())
2836 0 : nodes_with_nodesets.insert(std::get<0>(t));
2837 :
2838 : // To be filled in by BoundaryInfo calls in loop below
2839 0 : std::vector<boundary_id_type> node_boundary_ids;
2840 :
2841 0 : for(dof_id_type node_id : nodes_with_nodesets)
2842 : {
2843 0 : const Node * node = mesh.node_ptr(node_id);
2844 :
2845 0 : if (node->processor_id() != mesh.comm().rank())
2846 0 : continue;
2847 :
2848 0 : binfo.boundary_ids(node, node_boundary_ids);
2849 :
2850 0 : bool has_node_boundary_id = false;
2851 0 : for(boundary_id_type node_boundary_id : node_boundary_ids)
2852 0 : if(parametrized_function_boundary_ids.count(node_boundary_id))
2853 : {
2854 0 : has_node_boundary_id = true;
2855 0 : break;
2856 : }
2857 :
2858 0 : if(has_node_boundary_id)
2859 : {
2860 0 : gathered_local_node_ids.push_back(node_id);
2861 : }
2862 : }
2863 : }
2864 :
2865 : // I _think_ the local node ids are likely to already be sorted in
2866 : // ascending order, since that is how they are stored on the Mesh,
2867 : // but we can always just guarantee this to be on the safe side as
2868 : // well.
2869 0 : std::sort(gathered_local_node_ids.begin(), gathered_local_node_ids.end());
2870 :
2871 : // Gather the number of local nodes from all procs to proc 0
2872 0 : auto n_local_nodes = gathered_local_node_ids.size();
2873 0 : std::vector<std::size_t> gathered_n_local_nodes = {n_local_nodes};
2874 0 : sys.comm().gather(/*root_id=*/0, gathered_n_local_nodes);
2875 :
2876 : // Gather the node ids owned by each processor onto processor 0.
2877 0 : sys.comm().gather(/*root_id=*/0, gathered_local_node_ids);
2878 :
2879 : // Construct vectors of "start" and "one-past-the-end" indices into
2880 : // the gathered_local_node_ids vector for each proc. Only valid on
2881 : // processor 0.
2882 0 : std::vector<std::size_t> start_node_ids_index, end_node_ids_index;
2883 :
2884 0 : if (rank == 0)
2885 : {
2886 0 : start_node_ids_index.resize(n_procs);
2887 0 : start_node_ids_index[0] = 0;
2888 0 : for (processor_id_type p=1; p<n_procs; ++p)
2889 0 : start_node_ids_index[p] = start_node_ids_index[p-1] + gathered_n_local_nodes[p-1];
2890 :
2891 0 : end_node_ids_index.resize(n_procs);
2892 0 : end_node_ids_index[n_procs - 1] = gathered_local_node_ids.size();
2893 0 : for (processor_id_type p=0; p<n_procs - 1; ++p)
2894 0 : end_node_ids_index[p] = start_node_ids_index[p+1];
2895 : }
2896 :
2897 : // On processor 0, using basis function 0 and variable 0, prepare a
2898 : // vector with the nodes. Then scatter this vector
2899 : // out to the processors that require it. The order of this vector
2900 : // matches the gathered_local_node_ids ordering. The counts will be
2901 : // gathered_n_local_nodes.
2902 :
2903 : // On rank 0, the "counts" vector holds the number of floating point values that
2904 : // are to be scattered to each proc. It is only required on proc 0.
2905 0 : std::vector<int> counts;
2906 :
2907 0 : if (rank == 0)
2908 : {
2909 0 : counts.resize(n_procs);
2910 :
2911 0 : for (processor_id_type p=0; p<n_procs; ++p)
2912 : {
2913 0 : auto node_ids_range = (end_node_ids_index[p] - start_node_ids_index[p]);
2914 :
2915 : // Accumulate the count for this proc
2916 0 : counts[p] += node_ids_range;
2917 : } // end for proc_id
2918 : } // if (rank == 0)
2919 :
2920 : // The recv_node_data vector will be used on the receiving end of all
2921 : // the scatters below.
2922 0 : std::vector<Number> recv_node_data;
2923 :
2924 : // For each basis function and each variable, build a vector
2925 : // data in the Node ordering given by the
2926 : // gathered_local_node_ids, then call
2927 : //
2928 : // sys.comm().scatter(data, counts, recv, /*root_id=*/0);
2929 0 : std::vector<std::vector<Number>> node_data(n_vars);
2930 :
2931 : // We also reserve space in node_data, since we will push_back into it below.
2932 0 : int count_sum = std::accumulate(counts.begin(), counts.end(), 0);
2933 0 : for (auto var : index_range(node_data))
2934 0 : node_data[var].reserve(count_sum);
2935 :
2936 : // Loop from 0..n_bf on _all_ procs, since the scatters inside this
2937 : // loop are collective.
2938 0 : for (auto bf : make_range(n_bf))
2939 : {
2940 : // Prepare data for scattering (only on proc 0)
2941 0 : if (rank == 0)
2942 : {
2943 : // Reference to the data map for the current basis function.
2944 0 : auto & bf_map = _local_node_eim_basis_functions[bf];
2945 :
2946 : // Clear any data from previous bf (this does not change the capacity
2947 : // that was reserved above).
2948 0 : for (auto var : index_range(node_data))
2949 0 : node_data[var].clear();
2950 :
2951 0 : for (processor_id_type p=0; p<n_procs; ++p)
2952 : {
2953 0 : for (auto n : make_range(start_node_ids_index[p], end_node_ids_index[p]))
2954 : {
2955 0 : auto node_id = gathered_local_node_ids[n];
2956 :
2957 : // Get reference to array[n_vars] for current Node.
2958 : // Throws an error if the required node_id is not found.
2959 0 : const auto & array = libmesh_map_find(bf_map, node_id);
2960 :
2961 0 : for (auto var : index_range(array))
2962 0 : node_data[var].push_back(array[var]);
2963 : } // end for (n)
2964 : } // end for proc_id
2965 : } // end if rank==0
2966 :
2967 : // Perform the scatters (all procs)
2968 0 : for (auto var : make_range(n_vars))
2969 : {
2970 : // Do the scatter for the current var
2971 0 : sys.comm().scatter(node_data[var], counts, recv_node_data, /*root_id=*/0);
2972 :
2973 0 : if (rank != 0)
2974 : {
2975 : // Store the scattered data we received in _local_eim_basis_functions[bf]
2976 0 : auto & bf_map = _local_node_eim_basis_functions[bf];
2977 0 : auto cursor = recv_node_data.begin();
2978 :
2979 0 : for (auto i : index_range(gathered_local_node_ids))
2980 : {
2981 0 : auto node_id = gathered_local_node_ids[i];
2982 0 : auto & array = bf_map[node_id];
2983 :
2984 : // Create space to store the data if it doesn't already exist.
2985 0 : if (array.empty())
2986 0 : array.resize(n_vars);
2987 :
2988 : // There is only one value per variable per node, so
2989 : // we set the value by de-referencing cursor, and
2990 : // then advance the cursor by 1.
2991 0 : array[var] = *cursor;
2992 0 : std::advance(cursor, 1);
2993 : }
2994 : } // if (rank != 0)
2995 : } // end for (var)
2996 : } // end for (bf)
2997 :
2998 : // Now that the scattering is done, delete non-local Elem
2999 : // information from processor 0's _local_eim_basis_functions data
3000 : // structure.
3001 0 : if (rank == 0)
3002 : {
3003 0 : for (processor_id_type p=1; p<n_procs; ++p)
3004 : {
3005 0 : for (auto n : make_range(start_node_ids_index[p], end_node_ids_index[p]))
3006 : {
3007 0 : auto node_id = gathered_local_node_ids[n];
3008 :
3009 : // Delete this Node's information from every basis function.
3010 0 : for (auto & bf_map : _local_node_eim_basis_functions)
3011 0 : bf_map.erase(node_id);
3012 : } // end for (n)
3013 : } // end for proc_id
3014 : } // if (rank == 0)
3015 0 : }
3016 :
3017 0 : void RBEIMEvaluation::project_qp_data_vector_onto_system(System & /*sys*/,
3018 : const std::vector<Number> & /*bf_data*/,
3019 : const EIMVarGroupPlottingInfo & /*eim_vargroup*/,
3020 : const std::map<std::string,std::string> & /*extra_options*/)
3021 : {
3022 : // No-op by default, implement in subclasses if needed
3023 0 : }
3024 :
3025 0 : const std::vector<EIMVarGroupPlottingInfo> & RBEIMEvaluation::get_eim_vars_to_project_and_write() const
3026 : {
3027 0 : return _eim_vars_to_project_and_write;
3028 : }
3029 :
3030 0 : const std::set<unsigned int> & RBEIMEvaluation::scale_components_in_enrichment() const
3031 : {
3032 0 : return _scale_components_in_enrichment;
3033 : }
3034 :
3035 0 : bool RBEIMEvaluation::use_eim_error_indicator() const
3036 : {
3037 : // Return false by default, but we override this in subclasses
3038 : // for cases where we want to use the error indicator.
3039 0 : return false;
3040 : }
3041 :
3042 0 : void RBEIMEvaluation::set_eim_error_indicator_active(bool is_active)
3043 : {
3044 : // We skip setting _is_eim_error_indicator_active in the case that
3045 : // we have no parameters, since we do not use the EIM error indicator
3046 : // in that case. We also check if the number of interpolation points
3047 : // is larger than the number of EIM basis functions, since that is
3048 : // also always the case when the error indicator is active.
3049 0 : if ((get_n_params() > 0) && (get_n_interpolation_points() > get_n_basis_functions()))
3050 0 : _is_eim_error_indicator_active = (is_active && use_eim_error_indicator());
3051 0 : }
3052 :
3053 0 : std::pair<Real,Real> RBEIMEvaluation::get_eim_error_indicator(
3054 : Number error_indicator_rhs,
3055 : const DenseVector<Number> & eim_solution,
3056 : const DenseVector<Number> & eim_rhs)
3057 : {
3058 0 : DenseVector<Number> coeffs;
3059 0 : _error_indicator_interpolation_row.get_principal_subvector(eim_solution.size(), coeffs);
3060 :
3061 0 : Number EIM_val_at_error_indicator_pt = coeffs.dot(eim_solution);
3062 : Real error_indicator_val =
3063 0 : std::real(error_indicator_rhs - EIM_val_at_error_indicator_pt);
3064 :
3065 0 : Real normalization = 0.;
3066 0 : if (eim_error_indicator_normalization == RESIDUAL_SUM)
3067 : {
3068 : // This normalization is based on the sum of terms from the "EIM residual" calculation
3069 : // used in the calculation of error_indicator_val. This ensures that the error indicator
3070 : // will always be less than or equal to one, which is a useful property for an error
3071 : // indicator.
3072 0 : normalization =
3073 0 : std::abs(error_indicator_rhs) + std::abs(EIM_val_at_error_indicator_pt);
3074 : }
3075 0 : else if (eim_error_indicator_normalization == RESIDUAL_RHS)
3076 : {
3077 : // Normalize with respect to the right-hand side from the "EIM residual" calculation.
3078 0 : normalization = std::abs(error_indicator_rhs);
3079 : }
3080 0 : else if (eim_error_indicator_normalization == MAX_RHS)
3081 : {
3082 : // Normalize the error indicator based on the max-norm of the EIM RHS vector.
3083 : // This approach handles the case where different EIM variables have different
3084 : // magnitudes well, i.e. if error_indicator_val is based on a
3085 : // "small magnitude" variable, then by normalizing based on the entire
3086 : // RHS vector (which will typically include values from multiple different
3087 : // EIM variables) we will effectively scale down the error indicator
3088 : // corresponding to small variables, which is typically what we want.
3089 0 : normalization = std::max(eim_rhs.linfty_norm(), std::abs(error_indicator_rhs));
3090 : }
3091 : else
3092 : {
3093 0 : libmesh_error_msg("unsupported eim_error_indicator_normalization");
3094 : }
3095 :
3096 : // We avoid NaNs by setting normalization to 1 in the case that it is exactly 0.
3097 : // But we return the "original normalization" as well (as opposed to the modified
3098 : // normalization) since that can be useful information since it can indicate that
3099 : // the EIM approximation was identically zero, for example.
3100 0 : Real orig_normalization = normalization;
3101 0 : if (normalization == 0.)
3102 0 : normalization = 1.;
3103 :
3104 : // Return the relative error indicator, and the normalization that we used. By returning
3105 : // the normalization, we can subsequently recover the absolute error indicator if
3106 : // desired.
3107 : //
3108 : // We also optionally clamp the relative error indicator to 1.0 since this typically
3109 : // indicators 100% error and hence it may be the maximum value that we want to see.
3110 0 : Real rel_error_indicator = std::abs(error_indicator_val) / normalization;
3111 0 : if (limit_eim_error_indicator_to_one && (rel_error_indicator > 1.0))
3112 0 : rel_error_indicator = 1.0;
3113 :
3114 0 : return std::make_pair(rel_error_indicator, orig_normalization);
3115 : }
3116 :
3117 0 : const VectorizedEvalInput & RBEIMEvaluation::get_vec_eval_input() const
3118 : {
3119 0 : return _vec_eval_input;
3120 : }
3121 :
3122 0 : void RBEIMEvaluation::initialize_rb_property_map()
3123 : {
3124 0 : const auto & rb_property_map = get_parametrized_function().get_rb_property_map();
3125 : // Initialize rb_eim_eval VectorizedEvaluateInput from the one in rb_parametrized_function with
3126 : // empty sets as it will be filled by subclasses virtual functions.
3127 0 : for (const auto & [key, val] : rb_property_map)
3128 0 : _vec_eval_input.rb_property_map[key] = {};
3129 0 : }
3130 :
3131 0 : const DenseVector<Number> & RBEIMEvaluation::get_error_indicator_interpolation_row() const
3132 : {
3133 0 : return _error_indicator_interpolation_row;
3134 : }
3135 :
3136 0 : void RBEIMEvaluation::set_error_indicator_interpolation_row(const DenseVector<Number> & extra_point_row)
3137 : {
3138 0 : _error_indicator_interpolation_row = extra_point_row;
3139 0 : }
3140 :
3141 : } // namespace libMesh
|