libMesh
Loading...
Searching...
No Matches
rb_eim_evaluation.C
Go to the documentation of this file.
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"
38
39// C++ includes
40#include <sstream>
41#include <fstream>
42#include <numeric> // std::accumulate
43#include <iterator> // std::advance
44
45namespace libMesh
46{
47
49:
50ParallelObject(comm),
51eim_error_indicator_normalization(RBEIMEvaluation::MAX_RHS),
52limit_eim_error_indicator_to_one(true),
53_rb_eim_solves_N(0),
54_preserve_rb_eim_solutions(false),
55_is_eim_error_indicator_active(false)
56{
57}
58
60
62{
64
67
68
70
71 // Delete any RBTheta objects that were created
73}
74
75void RBEIMEvaluation::resize_data_structures(const unsigned int Nmax)
76{
77 // Resize the data structures relevant to the EIM system
80
83
85}
86
87void RBEIMEvaluation::set_parametrized_function(std::unique_ptr<RBParametrizedFunction> pf)
88{
89 _parametrized_function = std::move(pf);
90}
91
93{
94 libmesh_error_msg_if(!_parametrized_function, "Parametrized function not initialized yet");
95
97}
98
100{
101 libmesh_error_msg_if(!_parametrized_function, "Parametrized function not initialized yet");
102
104}
105
107{
108 LOG_SCOPE("rb_eim_solve()", "RBEIMEvaluation");
109
110 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 libmesh_error_msg_if(EIM_rhs.size()==0, "Error: N must be greater than 0 in rb_solve");
114
115 const unsigned int N = EIM_rhs.size();
116 DenseVector<Number> rb_eim_solution(N);
117 DenseMatrix<Number> interpolation_matrix_N;
118 _interpolation_matrix.get_principal_submatrix(N, interpolation_matrix_N);
119
120 interpolation_matrix_N.lu_solve(EIM_rhs, rb_eim_solution);
121
122 return rb_eim_solution;
123}
124
125void RBEIMEvaluation::rb_eim_solves(const std::vector<RBParameters> & mus,
126 unsigned int N)
127{
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 return;
136 }
137
138 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 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 if ((_rb_eim_solves_mus == mus) && (_rb_eim_solves_N == N))
144 return;
145
146 LOG_SCOPE("rb_eim_solves()", "RBEIMEvaluation");
147
148 _rb_eim_solves_mus = mus;
150
151 if (get_parametrized_function().is_lookup_table)
152 {
153 _rb_eim_solutions.resize(mus.size());
154 for (auto mu_index : index_range(mus))
155 {
156 Real lookup_table_param =
157 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 cast_int<unsigned int>(std::round(lookup_table_param));
163
164 DenseVector<Number> values;
165 _eim_solutions_for_training_set[lookup_table_index].get_principal_subvector(N, values);
166 _rb_eim_solutions[mu_index] = values;
167 }
168
169 return;
170 }
171
172 // output all comps indexing is as follows:
173 // mu index --> interpolation point index --> component index --> value.
174 std::vector<std::vector<std::vector<Number>>> output_all_comps;
175 if (get_parametrized_function().on_mesh_sides())
177 else if (get_parametrized_function().on_mesh_nodes())
179 else
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 auto n_samples_0 = mus[0].n_samples();
187 for (const auto & mu : mus)
188 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 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 if (num_rb_eim_solves == 0 && mus[0].n_parameters() == 0)
201 {
202 libmesh_error_msg_if(mus.size() != 1, "Must pass in only a single RBParameters object when solving with no parameters.");
203 num_rb_eim_solves = 1;
204 }
205
206 std::vector<std::vector<Number>> evaluated_values_at_interp_points(num_rb_eim_solves);
207
208 std::vector<Number> evaluated_values_at_err_indicator_point;
210 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 unsigned int counter = 0;
217 for (auto mu_index : index_range(mus))
218 for (auto sample_index : make_range(mus[mu_index].n_samples()))
219 {
220 // Ignore compiler warnings about unused loop index
221 libmesh_ignore(sample_index);
222
223 evaluated_values_at_interp_points[counter].resize(N);
224
225 for (unsigned int interp_pt_index=0; interp_pt_index<N; interp_pt_index++)
226 {
227 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 evaluated_values_at_interp_points[counter][interp_pt_index] =
232 output_all_comps[counter][interp_pt_index][comp];
233 }
234
236 {
237 unsigned int comp = _interpolation_points_comp[N];
238
239 evaluated_values_at_err_indicator_point[counter] =
240 output_all_comps[counter][N][comp];
241 }
242
243 counter++;
244 }
245
246 // Throw an error if we didn't do the required number of solves for
247 // some reason
248 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 DenseMatrix<Number> interpolation_matrix_N;
254 _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 _rb_eim_solutions.resize(num_rb_eim_solves);
261 _rb_eim_error_indicators.resize(num_rb_eim_solves);
262
263 {
264 unsigned int counter = 0;
265 for (auto mu_index : index_range(mus))
266 for (auto sample_index : make_range(mus[mu_index].n_samples()))
267 {
268 // Ignore compiler warnings about unused loop index
269 libmesh_ignore(sample_index);
270
271 DenseVector<Number> EIM_rhs = evaluated_values_at_interp_points[counter];
272 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.
279 {
280 Number error_indicator_rhs = evaluated_values_at_err_indicator_point[counter];
281 _rb_eim_error_indicators[counter] =
283 error_indicator_rhs, _rb_eim_solutions[counter], EIM_rhs);
284 }
285
286 counter++;
287 }
288 }
289}
290
298
304
306{
307 if (get_parametrized_function().on_mesh_sides())
309 else if (get_parametrized_function().on_mesh_nodes())
311 else
312 return _local_eim_basis_functions.size();
313}
314
316{
317 return _vec_eval_input.all_xyz.size();
318}
319
321{
323}
324
326{
327 return _vec_eval_input.rb_property_map.size();
328}
329
331{
332 if (get_parametrized_function().on_mesh_sides())
334 else if (get_parametrized_function().on_mesh_nodes())
336 else
337 _local_eim_basis_functions.resize(n_bfs);
338}
339
341 const DenseVector<Number> & coeffs)
342{
343 LOG_SCOPE("decrement_vector()", "RBEIMEvaluation");
344
345 libmesh_error_msg_if(get_n_basis_functions() != coeffs.size(),
346 "Error: Number of coefficients should match number of basis functions");
347
348 for (auto & [elem_id, v_comp_and_qp] : v)
349 {
350 for (const auto & comp : index_range(v_comp_and_qp))
351 for (unsigned int qp : index_range(v_comp_and_qp[comp]))
352 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 const auto & basis_comp_and_qp = libmesh_map_find(_local_eim_basis_functions[i], elem_id);
357
358 libmesh_error_msg_if(comp >= basis_comp_and_qp.size(), "Error: Invalid comp");
359 libmesh_error_msg_if(qp >= basis_comp_and_qp[comp].size(), "Error: Invalid qp");
360
361 v_comp_and_qp[comp][qp] -= coeffs(i) * basis_comp_and_qp[comp][qp];
362 }
363 }
364}
365
367 const DenseVector<Number> & coeffs)
368{
369 LOG_SCOPE("side_decrement_vector()", "RBEIMEvaluation");
370
371 libmesh_error_msg_if(get_n_basis_functions() != coeffs.size(),
372 "Error: Number of coefficients should match number of basis functions");
373
374 for (auto & [elem_and_side, v_comp_and_qp] : v)
375 {
376 for (const auto & comp : index_range(v_comp_and_qp))
377 for (unsigned int qp : index_range(v_comp_and_qp[comp]))
378 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 const auto & basis_comp_and_qp = libmesh_map_find(_local_side_eim_basis_functions[i], elem_and_side);
383
384 libmesh_error_msg_if(comp >= basis_comp_and_qp.size(), "Error: Invalid comp");
385 libmesh_error_msg_if(qp >= basis_comp_and_qp[comp].size(), "Error: Invalid qp");
386
387 v_comp_and_qp[comp][qp] -= coeffs(i) * basis_comp_and_qp[comp][qp];
388 }
389 }
390}
391
393 const DenseVector<Number> & coeffs)
394{
395 LOG_SCOPE("node_decrement_vector()", "RBEIMEvaluation");
396
397 libmesh_error_msg_if(get_n_basis_functions() != coeffs.size(),
398 "Error: Number of coefficients should match number of basis functions");
399
400 for (auto & [node_id, v_comps] : v)
401 {
402 for (const auto & comp : index_range(v_comps))
403 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 const auto & basis_comp = libmesh_map_find(_local_node_eim_basis_functions[i], node_id);
408
409 libmesh_error_msg_if(comp >= basis_comp.size(), "Error: Invalid comp");
410
411 v_comps[comp] -= coeffs(i) * basis_comp[comp];
412 }
413 }
414}
415
417{
418 // Initialize the rb_theta objects that access the solution from this rb_eim_evaluation
419 _rb_eim_theta_objects.clear();
420 for (auto i : make_range(get_n_basis_functions()))
421 _rb_eim_theta_objects.emplace_back(build_eim_theta(i));
422}
423
424std::vector<std::unique_ptr<RBTheta>> & RBEIMEvaluation::get_eim_theta_objects()
425{
427}
428
429std::unique_ptr<RBTheta> RBEIMEvaluation::build_eim_theta(unsigned int index)
430{
431 return std::make_unique<RBEIMTheta>(*this, index);
432}
433
435 const QpDataMap & pf,
436 dof_id_type elem_id,
437 unsigned int comp,
438 std::vector<Number> & values)
439{
440 LOG_SCOPE("get_parametrized_function_values_at_qps()", "RBEIMConstruction");
441
442 values.clear();
443
444 if (const auto it = pf.find(elem_id);
445 it != pf.end())
446 {
447 const auto & comps_and_qps_on_elem = it->second;
448 libmesh_error_msg_if(comp >= comps_and_qps_on_elem.size(),
449 "Invalid comp index: " << comp);
450
451 values = comps_and_qps_on_elem[comp];
452 }
453}
454
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 LOG_SCOPE("get_parametrized_function_side_values_at_qps()", "RBEIMConstruction");
463
464 values.clear();
465
466 if (const auto it = pf.find(std::make_pair(elem_id, side_index));
467 it != pf.end())
468 {
469 const auto & comps_and_qps_on_elem = it->second;
470 libmesh_error_msg_if(comp >= comps_and_qps_on_elem.size(),
471 "Invalid comp index: " << comp);
472
473 values = comps_and_qps_on_elem[comp];
474 }
475}
476
478 const NodeDataMap & pf,
479 dof_id_type node_id,
480 unsigned int comp)
481{
482 LOG_SCOPE("get_parametrized_function_node_local_value()", "RBEIMConstruction");
483
484 if (const auto it = pf.find(node_id);
485 it != pf.end())
486 {
487 const std::vector<Number> & vec = it->second;
488 libmesh_error_msg_if (comp >= vec.size(), "Error: Invalid comp index");
489 return vec[comp];
490 }
491 else
492 return 0.;
493}
494
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 std::vector<Number> values;
503 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 Number value = 0.;
507 if (!values.empty())
508 {
509 libmesh_error_msg_if(qp >= values.size(), "Error: Invalid qp index");
510
511 value = values[qp];
512 }
513 comm.sum(value);
514
515 return value;
516}
517
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 std::vector<Number> values;
527 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 Number value = 0.;
531 if (!values.empty())
532 {
533 libmesh_error_msg_if(qp >= values.size(), "Error: Invalid qp index");
534
535 value = values[qp];
536 }
537 comm.sum(value);
538
539 return value;
540}
541
543 const Parallel::Communicator & comm,
544 const NodeDataMap & pf,
545 dof_id_type node_id,
546 unsigned int comp)
547{
548 LOG_SCOPE("get_parametrized_function_node_value()", "RBEIMConstruction");
549
551 comm.sum(value);
552
553 return value;
554}
555
556void 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 libmesh_error_msg_if(basis_function_index >= _local_eim_basis_functions.size(),
562 "Invalid basis function index: " << basis_function_index);
563
565 _local_eim_basis_functions[basis_function_index],
566 elem_id,
567 comp,
568 values);
569}
570
572 dof_id_type elem_id,
573 unsigned int side_index,
574 unsigned int comp,
575 std::vector<Number> & values) const
576{
577 libmesh_error_msg_if(basis_function_index >= _local_side_eim_basis_functions.size(),
578 "Invalid basis function index: " << basis_function_index);
579
581 _local_side_eim_basis_functions[basis_function_index],
582 elem_id,
583 side_index,
584 comp,
585 values);
586}
587
589 dof_id_type node_id,
590 unsigned int comp) const
591{
592 libmesh_error_msg_if(basis_function_index >= _local_node_eim_basis_functions.size(),
593 "Invalid basis function index: " << basis_function_index);
594
596 _local_node_eim_basis_functions[basis_function_index],
597 node_id,
598 comp);
599}
600
602 dof_id_type node_id,
603 unsigned int comp) const
604{
605 libmesh_error_msg_if(basis_function_index >= _local_node_eim_basis_functions.size(),
606 "Invalid basis function index: " << basis_function_index);
607
609 comm(),
610 _local_node_eim_basis_functions[basis_function_index],
611 node_id,
612 comp);
613}
614
616 dof_id_type elem_id,
617 unsigned int comp,
618 unsigned int qp) const
619{
620 libmesh_error_msg_if(basis_function_index >= _local_eim_basis_functions.size(),
621 "Invalid basis function index: " << basis_function_index);
622
624 comm(),
625 _local_eim_basis_functions[basis_function_index],
626 elem_id,
627 comp,
628 qp);
629}
630
632 dof_id_type elem_id,
633 unsigned int side_index,
634 unsigned int comp,
635 unsigned int qp) const
636{
637 libmesh_error_msg_if(basis_function_index >= _local_side_eim_basis_functions.size(),
638 "Invalid side basis function index: " << basis_function_index);
639
641 comm(),
642 _local_side_eim_basis_functions[basis_function_index],
643 elem_id,
644 side_index,
645 comp,
646 qp);
647}
648
651{
653}
654
660
666
667void RBEIMEvaluation::set_rb_eim_solutions(const std::vector<DenseVector<Number>> & rb_eim_solutions)
668{
669 _rb_eim_solutions = rb_eim_solutions;
670}
671
672const std::vector<DenseVector<Number>> & RBEIMEvaluation::get_rb_eim_solutions() const
673{
674 return _rb_eim_solutions;
675}
676
677std::vector<Number> RBEIMEvaluation::get_rb_eim_solutions_entries(unsigned int index) const
678{
679 LOG_SCOPE("get_rb_eim_solutions_entries()", "RBEIMEvaluation");
680
681 std::vector<Number> rb_eim_solutions_entries(_rb_eim_solutions.size());
682 for (unsigned int mu_index : index_range(_rb_eim_solutions))
683 {
684 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 rb_eim_solutions_entries[mu_index] = _rb_eim_solutions[mu_index](index);
688 }
689
690 return rb_eim_solutions_entries;
691}
692
693const std::vector<DenseVector<Number>> & RBEIMEvaluation::get_eim_solutions_for_training_set() const
694{
696}
697
698std::vector<DenseVector<Number>> & RBEIMEvaluation::get_eim_solutions_for_training_set()
699{
701}
702
703const std::vector<std::pair<Real,Real>> & RBEIMEvaluation::get_rb_eim_error_indicators() const
704{
706}
707
712
714{
715 _interpolation_points_comp.emplace_back(comp);
716}
717
722
727
728void RBEIMEvaluation::add_interpolation_points_xyz_perturbations(const std::vector<Point> & perturbs)
729{
730 _vec_eval_input.all_xyz_perturb.emplace_back(perturbs);
731}
732
737
739{
740 _vec_eval_input.side_indices.emplace_back(side_index);
741}
742
747
749{
750 _vec_eval_input.qps.emplace_back(qp);
751}
752
754{
755 _vec_eval_input.elem_types.emplace_back(elem_type);
756}
757
758void RBEIMEvaluation::add_interpolation_points_JxW_all_qp(const std::vector<Real> & JxW_all_qp)
759{
760 _vec_eval_input.JxW_all_qp.emplace_back(JxW_all_qp);
761}
762
763void RBEIMEvaluation::add_interpolation_points_phi_i_all_qp(const std::vector<std::vector<Real>> & phi_i_all_qp)
764{
765 _vec_eval_input.phi_i_all_qp.emplace_back(phi_i_all_qp);
766}
767
768void RBEIMEvaluation::add_interpolation_points_phi_i_qp(const std::vector<Real> & phi_i_qp)
769{
770 _vec_eval_input.phi_i_qp.emplace_back(phi_i_qp);
771}
772
774{
775 _vec_eval_input.dxyzdxi_elem_center.emplace_back(dxyzdxi);
776}
777
779{
780 _vec_eval_input.dxyzdeta_elem_center.emplace_back(dxyzdeta);
781}
782
784{
785 _vec_eval_input.qrule_orders.emplace_back(qrule_order);
786}
787
788void RBEIMEvaluation::add_interpolation_points_spatial_indices(const std::vector<unsigned int> & spatial_indices)
789{
790 _interpolation_points_spatial_indices.emplace_back(spatial_indices);
791}
792
794{
795 // Try to insert object and return an error if object already inserted as duplicated should not happen.
796 bool insert_succeed = _vec_eval_input.elem_id_to_local_index.insert({elem_id, local_index}).second;
797 libmesh_error_msg_if(!insert_succeed, "Entry already added, duplicate detected.");
798}
799
800void RBEIMEvaluation::add_rb_property_map_entry(std::string & property_name, std::set<dof_id_type> & entity_ids)
801{
802 bool insert_succeed = _vec_eval_input.rb_property_map.insert({property_name, entity_ids}).second;
803 libmesh_error_msg_if(!insert_succeed, "Entry already added, duplicate detected.");
804}
805
807{
808 libmesh_error_msg_if(index >= _vec_eval_input.all_xyz.size(), "Error: Invalid index");
809
810 return _vec_eval_input.all_xyz[index];
811}
812
813unsigned int RBEIMEvaluation::get_interpolation_points_comp(unsigned int index) const
814{
815 libmesh_error_msg_if(index >= _interpolation_points_comp.size(), "Error: Invalid index");
816
817 return _interpolation_points_comp[index];
818}
819
821{
822 libmesh_error_msg_if(index >= _vec_eval_input.sbd_ids.size(), "Error: Invalid index");
823
824 return _vec_eval_input.sbd_ids[index];
825}
826
828{
829 libmesh_error_msg_if(index >= _vec_eval_input.boundary_ids.size(), "Error: Invalid index");
830
831 return _vec_eval_input.boundary_ids[index];
832}
833
834const std::vector<Point> & RBEIMEvaluation::get_interpolation_points_xyz_perturbations(unsigned int index) const
835{
836 libmesh_error_msg_if(index >= _vec_eval_input.all_xyz_perturb.size(), "Error: Invalid index");
837
838 return _vec_eval_input.all_xyz_perturb[index];
839}
840
842{
843 libmesh_error_msg_if(index >= _vec_eval_input.elem_ids.size(), "Error: Invalid index");
844
845 return _vec_eval_input.elem_ids[index];
846}
847
848unsigned int RBEIMEvaluation::get_interpolation_points_side_index(unsigned int index) const
849{
850 libmesh_error_msg_if(index >= _vec_eval_input.side_indices.size(), "Error: Invalid index");
851
852 return _vec_eval_input.side_indices[index];
853}
854
856{
857 libmesh_error_msg_if(index >= _vec_eval_input.node_ids.size(), "Error: Invalid index");
858
859 return _vec_eval_input.node_ids[index];
860}
861
862unsigned int RBEIMEvaluation::get_interpolation_points_qp(unsigned int index) const
863{
864 libmesh_error_msg_if(index >= _vec_eval_input.qps.size(), "Error: Invalid index");
865
866 return _vec_eval_input.qps[index];
867}
868
870{
871 libmesh_error_msg_if(index >= _vec_eval_input.elem_types.size(), "Error: Invalid index");
872
873 return _vec_eval_input.elem_types[index];
874}
875
876const std::vector<Real> & RBEIMEvaluation::get_interpolation_points_JxW_all_qp(unsigned int index) const
877{
878 libmesh_error_msg_if(index >= _vec_eval_input.JxW_all_qp.size(), "Error: Invalid index");
879
880 return _vec_eval_input.JxW_all_qp[index];
881}
882
883const std::map<dof_id_type, unsigned int> & RBEIMEvaluation::get_elem_id_to_local_index_map() const
884{
886}
887
888const std::unordered_map<std::string, std::set<dof_id_type>> & RBEIMEvaluation::get_rb_property_map() const
889{
891}
892
893const std::vector<std::vector<Real>> & RBEIMEvaluation::get_interpolation_points_phi_i_all_qp(unsigned int index) const
894{
895 libmesh_error_msg_if(index >= _vec_eval_input.phi_i_all_qp.size(), "Error: Invalid index");
896
897 return _vec_eval_input.phi_i_all_qp[index];
898}
899
900const std::vector<Real> & RBEIMEvaluation::get_interpolation_points_phi_i_qp(unsigned int index) const
901{
902 libmesh_error_msg_if(index >= _vec_eval_input.phi_i_qp.size(), "Error: Invalid index");
903
904 return _vec_eval_input.phi_i_qp[index];
905}
906
907const Point & RBEIMEvaluation::get_elem_center_dxyzdxi(unsigned int index) const
908{
909 libmesh_error_msg_if(index >= _vec_eval_input.dxyzdxi_elem_center.size(), "Error: Invalid index");
910
912}
913
914const Point & RBEIMEvaluation::get_elem_center_dxyzdeta(unsigned int index) const
915{
916 libmesh_error_msg_if(index >= _vec_eval_input.dxyzdeta_elem_center.size(), "Error: Invalid index");
917
919}
920
922{
923 libmesh_error_msg_if(index >= _vec_eval_input.qrule_orders.size(), "Error: Invalid index");
924
925 return _vec_eval_input.qrule_orders[index];
926}
927
928const std::vector<unsigned int> & RBEIMEvaluation::get_interpolation_points_spatial_indices(unsigned int index) const
929{
930 libmesh_error_msg_if(index >= _interpolation_points_spatial_indices.size(), "Error: Invalid index");
931
933}
934
939
941{
942 libmesh_error_msg_if((i >= _interpolation_matrix.m()) || (j >= _interpolation_matrix.n()),
943 "Error: Invalid matrix indices");
944
946}
947
952
953void RBEIMEvaluation::set_preserve_rb_eim_solutions(bool preserve_rb_eim_solutions)
954{
955 _preserve_rb_eim_solutions = preserve_rb_eim_solutions;
956}
957
962
964 const QpDataMap & bf)
965{
966 _local_eim_basis_functions.emplace_back(bf);
967}
968
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 _vec_eval_input.all_xyz.emplace_back(p);
985 _interpolation_points_comp.emplace_back(comp);
986 _vec_eval_input.elem_ids.emplace_back(elem_id);
987 _vec_eval_input.sbd_ids.emplace_back(subdomain_id);
988 _vec_eval_input.qps.emplace_back(qp);
989 _vec_eval_input.all_xyz_perturb.emplace_back(perturbs);
990 _vec_eval_input.phi_i_qp.emplace_back(phi_i_qp);
991 _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 if (_vec_eval_input.elem_id_to_local_index.count(elem_id) == 0)
997 {
998 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 _vec_eval_input.elem_id_to_local_index[elem_id] = local_index;
1001 _vec_eval_input.JxW_all_qp.emplace_back(JxW_all_qp);
1002 _vec_eval_input.phi_i_all_qp.emplace_back(phi_i_all_qp);
1003 _vec_eval_input.qrule_orders.emplace_back(qrule_order);
1004 _vec_eval_input.dxyzdxi_elem_center.emplace_back(dxyzdxi_elem_center);
1005 _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.
1015 elem_id);
1016}
1017
1019 const SideQpDataMap & side_bf)
1020{
1021 _local_side_eim_basis_functions.emplace_back(side_bf);
1022}
1023
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 _vec_eval_input.all_xyz.emplace_back(p);
1036 _interpolation_points_comp.emplace_back(comp);
1037 _vec_eval_input.elem_ids.emplace_back(elem_id);
1038 _vec_eval_input.side_indices.emplace_back(side_index);
1039 _vec_eval_input.sbd_ids.emplace_back(subdomain_id);
1040 _vec_eval_input.boundary_ids.emplace_back(boundary_id);
1041 _vec_eval_input.qps.emplace_back(qp);
1042 _vec_eval_input.all_xyz_perturb.emplace_back(perturbs);
1043 _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.
1048 _vec_eval_input.JxW_all_qp.emplace_back();
1049 _vec_eval_input.phi_i_all_qp.emplace_back();
1051 _vec_eval_input.dxyzdxi_elem_center.emplace_back();
1053}
1054
1056 const NodeDataMap & node_bf)
1057{
1058 _local_node_eim_basis_functions.emplace_back(node_bf);
1059}
1060
1062 Point p,
1063 unsigned int comp,
1064 dof_id_type node_id,
1065 boundary_id_type boundary_id)
1066{
1067 _vec_eval_input.all_xyz.emplace_back(p);
1068 _interpolation_points_comp.emplace_back(comp);
1069 _vec_eval_input.node_ids.emplace_back(node_id);
1070 _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 _vec_eval_input.elem_ids.emplace_back(0);
1075 _vec_eval_input.side_indices.emplace_back(0);
1076 _vec_eval_input.sbd_ids.emplace_back(0);
1077 _vec_eval_input.qps.emplace_back(0);
1078 _vec_eval_input.all_xyz_perturb.emplace_back();
1079 _vec_eval_input.phi_i_qp.emplace_back();
1081 _vec_eval_input.JxW_all_qp.emplace_back();
1082 _vec_eval_input.phi_i_all_qp.emplace_back();
1084 _vec_eval_input.dxyzdxi_elem_center.emplace_back();
1086}
1087
1089write_out_basis_functions(const std::string & directory_name,
1090 bool write_binary_basis_functions)
1091{
1092 LOG_SCOPE("write_out_basis_functions()", "RBEIMEvaluation");
1093
1094 if (get_parametrized_function().on_mesh_sides())
1095 write_out_side_basis_functions(directory_name, write_binary_basis_functions);
1096 else if (get_parametrized_function().on_mesh_nodes())
1097 write_out_node_basis_functions(directory_name, write_binary_basis_functions);
1098 else
1099 write_out_interior_basis_functions(directory_name, write_binary_basis_functions);
1100}
1101
1103write_out_interior_basis_functions(const std::string & directory_name,
1104 bool write_binary_basis_functions)
1105{
1106 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 bool is_empty = _local_eim_basis_functions.empty();
1111 libmesh_assert(this->comm().verify(is_empty));
1112
1113 if (is_empty)
1114 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 this->gather_bfs();
1120
1121 // Write values from processor 0 only.
1122 if (this->processor_id() == 0)
1123 {
1124 std::vector<unsigned int> n_qp_per_elem;
1125 auto interior_basis_function_sizes =
1126 get_interior_basis_function_sizes(n_qp_per_elem);
1127
1128 // Make a directory to store all the data files
1129 Utility::mkdir(directory_name.c_str());
1130
1131 // Create filename
1132 std::ostringstream file_name;
1133 const std::string basis_function_suffix = (write_binary_basis_functions ? ".xdr" : ".dat");
1134 file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1135
1136 // Create XDR writer object
1137 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 auto n_bf = libmesh_map_find(interior_basis_function_sizes,"n_bf");
1143 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 auto n_elem = libmesh_map_find(interior_basis_function_sizes,"n_elem");
1149 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 auto n_vars = libmesh_map_find(interior_basis_function_sizes,"n_vars");
1155 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 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 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 for (auto bf_index : index_range(_local_eim_basis_functions))
1171 {
1172 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 for (auto var : index_range(qp_data))
1176 xdr.data_stream(qp_data[var].data(), qp_data[var].size(), /*line_break=*/qp_data[var].size());
1177 }
1178 }
1179}
1180
1181std::map<std::string,std::size_t> RBEIMEvaluation::
1182get_interior_basis_function_sizes(std::vector<unsigned int> & n_qp_per_elem)
1183{
1184 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 auto n_bf = _local_eim_basis_functions.size();
1190 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 auto n_elem = _local_eim_basis_functions[0].size();
1196 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 auto n_vars = _local_eim_basis_functions[0].begin()->second.size();
1202 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 n_qp_per_elem.clear();
1208 n_qp_per_elem.reserve(n_elem);
1209 dof_id_type expected_elem_id = 0;
1210 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 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 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 std::accumulate(n_qp_per_elem.begin(),
1228 n_qp_per_elem.end(),
1229 0u);
1230 interior_basis_function_sizes["n_qp_data"] = n_qp_data;
1231
1232 return interior_basis_function_sizes;
1233}
1234
1235std::vector<std::vector<Number>> RBEIMEvaluation::
1237 unsigned int n_vars,
1238 unsigned int n_qp_data,
1239 unsigned int bf_index)
1240{
1241 LOG_SCOPE("get_interior_basis_function_as_vec_helper()", "RBEIMEvaluation");
1242
1243 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 for (auto var : index_range(qp_data))
1248 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 libmesh_error_msg_if(bf_index >= _local_eim_basis_functions.size(), "bf_index not valid");
1256 for (const auto & pr : _local_eim_basis_functions[bf_index])
1257 {
1258 // array[n_vars][n_qp] per Elem
1259 const auto & array = pr.second;
1260 for (auto var : index_range(array))
1261 {
1262 // Insert all qp values for this var
1263 qp_data[var].insert(/*insert at*/qp_data[var].end(),
1264 /*data start*/array[var].begin(),
1265 /*data end*/array[var].end());
1266 }
1267 }
1268
1269 return qp_data;
1270}
1271
1272std::vector<std::vector<std::vector<Number>>> RBEIMEvaluation::
1274{
1275 LOG_SCOPE("get_interior_basis_function_as_vec()", "RBEIMEvaluation");
1276
1277 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 bool is_empty = _local_eim_basis_functions.empty();
1282 libmesh_assert(this->comm().verify(is_empty));
1283
1284 if (is_empty)
1285 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 this->gather_bfs();
1291
1292 if (this->processor_id() == 0)
1293 {
1294 std::vector<unsigned int> n_qp_per_elem;
1295 std::map<std::string,std::size_t> interior_basis_function_sizes =
1296 get_interior_basis_function_sizes(n_qp_per_elem);
1297
1298 for (auto bf_index : index_range(_local_eim_basis_functions))
1299 interior_basis_functions.emplace_back(
1301 libmesh_map_find(interior_basis_function_sizes,"n_vars"),
1302 libmesh_map_find(interior_basis_function_sizes,"n_qp_data"),
1303 bf_index));
1304 }
1305
1306 return interior_basis_functions;
1307}
1308
1310write_out_side_basis_functions(const std::string & directory_name,
1311 bool write_binary_basis_functions)
1312{
1313 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 bool is_empty = _local_side_eim_basis_functions.empty();
1318 libmesh_assert(this->comm().verify(is_empty));
1319
1320 if (is_empty)
1321 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 this->side_gather_bfs();
1327
1328 // Write values from processor 0 only.
1329 if (this->processor_id() == 0)
1330 {
1331 // Make a directory to store all the data files
1332 Utility::mkdir(directory_name.c_str());
1333
1334 // Create filename
1335 std::ostringstream file_name;
1336 const std::string basis_function_suffix = (write_binary_basis_functions ? ".xdr" : ".dat");
1337 file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1338
1339 // Create XDR writer object
1340 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 auto n_bf = _local_side_eim_basis_functions.size();
1346 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 auto n_elem = _local_side_eim_basis_functions[0].size();
1352 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 auto n_vars = _local_side_eim_basis_functions[0].begin()->second.size();
1358 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 std::vector<unsigned int> n_qp_per_elem_side;
1365 std::vector<unsigned int> elem_ids;
1366 std::vector<unsigned int> side_indices;
1367 elem_ids.reserve(n_elem);
1368 side_indices.reserve(n_elem);
1369 n_qp_per_elem_side.reserve(n_elem);
1370 for (const auto & [elem_side_pair, array] : _local_side_eim_basis_functions[0])
1371 {
1372 elem_ids.push_back(elem_side_pair.first);
1373 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 n_qp_per_elem_side.push_back(array[0].size());
1378 }
1379 xdr.data(elem_ids, "# Elem IDs");
1380 xdr.data(side_indices, "# Side indices");
1381 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 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 std::vector<std::vector<Number>> qp_data(n_vars);
1392 for (auto var : index_range(qp_data))
1393 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.
1400 {
1401 // Clear any data from previous bf
1402 for (auto var : index_range(qp_data))
1403 qp_data[var].clear();
1404
1405 for (const auto & pr : _local_side_eim_basis_functions[bf])
1406 {
1407 // array[n_vars][n_qp] per Elem
1408 const auto & array = pr.second;
1409 for (auto var : index_range(array))
1410 {
1411 // Insert all qp values for this var
1412 qp_data[var].insert(/*insert at*/qp_data[var].end(),
1413 /*data start*/array[var].begin(),
1414 /*data end*/array[var].end());
1415 }
1416 }
1417
1418 // Write all the var values for this bf
1419 for (auto var : index_range(qp_data))
1420 xdr.data_stream(qp_data[var].data(), qp_data[var].size(), /*line_break=*/qp_data[var].size());
1421 }
1422 }
1423}
1424
1426write_out_node_basis_functions(const std::string & directory_name,
1427 bool write_binary_basis_functions)
1428{
1429 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 bool is_empty = _local_node_eim_basis_functions.empty();
1434 libmesh_assert(this->comm().verify(is_empty));
1435
1436 if (is_empty)
1437 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 this->node_gather_bfs();
1443
1444 // Write values from processor 0 only.
1445 if (this->processor_id() == 0)
1446 {
1447 // Make a directory to store all the data files
1448 Utility::mkdir(directory_name.c_str());
1449
1450 // Create filename
1451 std::ostringstream file_name;
1452 const std::string basis_function_suffix = (write_binary_basis_functions ? ".xdr" : ".dat");
1453 file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1454
1455 // Create XDR writer object
1456 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 auto n_bf = _local_node_eim_basis_functions.size();
1462 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 auto n_node = _local_node_eim_basis_functions[0].size();
1468 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 auto n_vars = _local_node_eim_basis_functions[0].begin()->second.size();
1474 xdr.data(n_vars, "# Number of variables");
1475
1476 // We write out the following arrays:
1477 // - node IDs
1478 std::vector<unsigned int> node_ids;
1479 node_ids.reserve(n_node);
1480 for (const auto & pr : _local_node_eim_basis_functions[0])
1481 {
1482 node_ids.push_back(pr.first);
1483 }
1484 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 std::vector<std::vector<Number>> var_data(n_vars);
1492 for (unsigned int var=0; var<n_vars; var++)
1493 var_data[var].resize(n_node);
1494
1496 {
1497 unsigned int node_counter = 0;
1498 for (const auto & pr : _local_node_eim_basis_functions[bf])
1499 {
1500 // array[n_vars] per Node
1501 const auto & array = pr.second;
1502 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 var_data[var][node_counter] = array[var];
1508 }
1509
1510 node_counter++;
1511 }
1512
1513 // Write all the var values for this bf
1514 for (auto var : index_range(var_data))
1515 xdr.data_stream(var_data[var].data(), var_data[var].size(), /*line_break=*/var_data[var].size());
1516 }
1517 }
1518}
1519
1522 const std::string & directory_name,
1523 bool read_binary_basis_functions)
1524{
1525 LOG_SCOPE("read_in_basis_functions()", "RBEIMEvaluation");
1526
1527 // Return early without reading in anything if there are no basis functions
1528 if (get_n_basis_functions() == 0)
1529 return;
1530
1531 if (get_parametrized_function().on_mesh_sides())
1532 read_in_side_basis_functions(sys, directory_name, read_binary_basis_functions);
1533 else if (get_parametrized_function().on_mesh_nodes())
1534 read_in_node_basis_functions(sys, directory_name, read_binary_basis_functions);
1535 else
1536 read_in_interior_basis_functions(sys, directory_name, read_binary_basis_functions);
1537}
1538
1541 const std::string & directory_name,
1542 bool read_binary_basis_functions)
1543{
1544 LOG_SCOPE("read_in_interior_basis_functions()", "RBEIMEvaluation");
1545
1546 // Read values on processor 0 only.
1547 if (sys.comm().rank() == 0)
1548 {
1549 // Create filename
1550 std::ostringstream file_name;
1551 const std::string basis_function_suffix = (read_binary_basis_functions ? ".xdr" : ".dat");
1552 file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1553
1554 // Create XDR reader object
1555 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 xdr.data(n_bf);
1561
1562 // Read in the number of elements
1563 std::size_t n_elem;
1564 xdr.data(n_elem);
1565
1566 // Read in the number of variables.
1567 std::size_t n_vars;
1568 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 std::vector<unsigned int> n_qp_per_elem(n_elem);
1574 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 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)?
1590 _local_eim_basis_functions.resize(n_bf);
1592 for (std::size_t elem_id=0; elem_id<n_elem; ++elem_id)
1593 {
1594 auto & array = _local_eim_basis_functions[i][elem_id];
1595 array.resize(n_vars);
1596 }
1597
1598 // Allocate temporary storage for one var's worth of qp data.
1599 std::vector<Number> qp_data;
1600
1601 // Read in data for each basis function
1603 {
1604 // Reference to the data map for the current basis function.
1605 auto & bf_map = _local_eim_basis_functions[i];
1606
1607 for (std::size_t var=0; var<n_vars; ++var)
1608 {
1609 qp_data.clear();
1610 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 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 auto cursor = qp_data.begin();
1620 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 auto & array = bf_map[elem_id];
1627 array[var].assign(cursor, cursor + n_qp_per_elem[elem_id]);
1628 std::advance(cursor, n_qp_per_elem[elem_id]);
1629 }
1630 } // end for (var)
1631 } // end for (i)
1632 } // end if processor 0
1633
1634 // Distribute the basis function information to the processors that require it
1635 this->distribute_bfs(sys);
1636}
1637
1640 const std::string & directory_name,
1641 bool read_binary_basis_functions)
1642{
1643 LOG_SCOPE("read_in_basis_functions()", "RBEIMEvaluation");
1644
1645 // Read values on processor 0 only.
1646 if (sys.comm().rank() == 0)
1647 {
1648 // Create filename
1649 std::ostringstream file_name;
1650 const std::string basis_function_suffix = (read_binary_basis_functions ? ".xdr" : ".dat");
1651 file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1652
1653 // Create XDR reader object
1654 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 xdr.data(n_bf);
1660
1661 // Read in the number of elements
1662 std::size_t n_elem_side;
1663 xdr.data(n_elem_side);
1664
1665 // Read in the number of variables.
1666 std::size_t n_vars;
1667 xdr.data(n_vars);
1668
1669 std::vector<unsigned int> elem_ids(n_elem_side);
1670 xdr.data(elem_ids);
1671 std::vector<unsigned int> side_indices(n_elem_side);
1672 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 std::vector<unsigned int> n_qp_per_elem_side(n_elem_side);
1678 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 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.
1692 for (std::size_t elem_side_idx=0; elem_side_idx<n_elem_side; ++elem_side_idx)
1693 {
1694 unsigned int elem_id = elem_ids[elem_side_idx];
1695 unsigned int side_index = side_indices[elem_side_idx];
1696 auto elem_side_pair = std::make_pair(elem_id, side_index);
1697
1698 auto & array = _local_side_eim_basis_functions[i][elem_side_pair];
1699 array.resize(n_vars);
1700 }
1701
1702 // Allocate temporary storage for one var's worth of qp data.
1703 std::vector<Number> qp_data;
1704
1705 // Read in data for each basis function
1707 {
1708 // Reference to the data map for the current basis function.
1709 auto & bf_map = _local_side_eim_basis_functions[i];
1710
1711 for (std::size_t var=0; var<n_vars; ++var)
1712 {
1713 qp_data.clear();
1714 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 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 auto cursor = qp_data.begin();
1724 for (std::size_t elem_side_idx=0; elem_side_idx<n_elem_side; ++elem_side_idx)
1725 {
1726 unsigned int elem_id = elem_ids[elem_side_idx];
1727 unsigned int side_index = side_indices[elem_side_idx];
1728 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 auto & array = bf_map[elem_side_pair];
1735 array[var].assign(cursor, cursor + n_qp_per_elem_side[elem_side_idx]);
1736 std::advance(cursor, n_qp_per_elem_side[elem_side_idx]);
1737 }
1738 } // end for (var)
1739 } // end for (i)
1740 } // end if processor 0
1741
1742 // Distribute the basis function information to the processors that require it
1743 this->side_distribute_bfs(sys);
1744}
1745
1748 const std::string & directory_name,
1749 bool read_binary_basis_functions)
1750{
1751 LOG_SCOPE("read_in_node_basis_functions()", "RBEIMEvaluation");
1752
1753 // Read values on processor 0 only.
1754 if (sys.comm().rank() == 0)
1755 {
1756 // Create filename
1757 std::ostringstream file_name;
1758 const std::string basis_function_suffix = (read_binary_basis_functions ? ".xdr" : ".dat");
1759 file_name << directory_name << "/" << "bf_data" << basis_function_suffix;
1760
1761 // Create XDR reader object
1762 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 xdr.data(n_bf);
1768
1769 // Read in the number of nodes
1770 std::size_t n_node;
1771 xdr.data(n_node);
1772
1773 // Read in the number of variables.
1774 std::size_t n_vars;
1775 xdr.data(n_vars);
1776
1777 std::vector<unsigned int> node_ids(n_node);
1778 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)?
1789 for (auto node_id : node_ids)
1790 {
1791 auto & array = _local_node_eim_basis_functions[i][node_id];
1792 array.resize(n_vars);
1793 }
1794
1795 // Read data into node_value from xdr
1796 std::vector<Number> node_value;
1797
1798 // Read in data for each basis function
1800 {
1801 // Reference to the data map for the current basis function.
1802 auto & bf_map = _local_node_eim_basis_functions[i];
1803
1804 for (std::size_t var=0; var<n_vars; ++var)
1805 {
1806 node_value.clear();
1807 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 xdr.data_stream(node_value.data(), node_value.size());
1813
1814 for (unsigned int node_counter=0; node_counter<n_node; node_counter++)
1815 {
1816 auto & array = bf_map[node_ids[node_counter]];
1817 array[var] = node_value[node_counter];
1818 }
1819 } // end for (var)
1820 } // end for (i)
1821 } // end if processor 0
1822
1823 // Distribute the basis function information to the processors that require it
1824 this->node_distribute_bfs(sys);
1825}
1826
1828{
1830 {
1831 libMesh::out << "Interior basis function " << bf << std::endl;
1832 for (const auto & [elem_id, array] : _local_eim_basis_functions[bf])
1833 {
1834 libMesh::out << "Elem " << elem_id << std::endl;
1835 for (auto var : index_range(array))
1836 {
1837 libMesh::out << "Variable " << var << std::endl;
1838 for (auto qp : index_range(array[var]))
1839 libMesh::out << array[var][qp] << " ";
1840 libMesh::out << std::endl;
1841 }
1842 }
1843 }
1844
1846 {
1847 libMesh::out << "Side basis function " << bf << std::endl;
1848 for (const auto & [pr, array] : _local_side_eim_basis_functions[bf])
1849 {
1850 const auto & elem_id = pr.first;
1851 const auto & side_index = pr.second;
1852 libMesh::out << "Elem " << elem_id << ", Side " << side_index << std::endl;
1853 for (auto var : index_range(array))
1854 {
1855 libMesh::out << "Variable " << var << std::endl;
1856 for (auto qp : index_range(array[var]))
1857 libMesh::out << array[var][qp] << " ";
1858 libMesh::out << std::endl;
1859 }
1860 }
1861 }
1862
1864 {
1865 libMesh::out << "Node basis function " << bf << std::endl;
1866 for (const auto & [node_id, array] : _local_node_eim_basis_functions[bf])
1867 {
1868 libMesh::out << "Node " << node_id << std::endl;
1869 for (auto var : index_range(array))
1870 {
1871 libMesh::out << "Variable " << var << ": " << array[var] << std::endl;
1872 }
1873 libMesh::out << std::endl;
1874 }
1875 }
1876}
1877
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 auto n_procs = this->n_processors();
1893
1894 // In serial there's nothing to gather
1895 if (n_procs == 1)
1896 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 auto n_bf = _local_eim_basis_functions.size();
1902 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 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 _local_eim_basis_functions[0].empty() ? 0 : _local_eim_basis_functions[0].begin()->second.size();
1916 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 std::vector<dof_id_type> elem_ids;
1922 elem_ids.reserve(_local_eim_basis_functions[0].size());
1923 for (const auto & pr : _local_eim_basis_functions[0])
1924 elem_ids.push_back(pr.first);
1925 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 std::vector<unsigned int> n_qp_per_elem;
1931 n_qp_per_elem.reserve(_local_eim_basis_functions[0].size());
1932 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 const auto & array = pr.second;
1937 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 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 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 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 std::vector<std::vector<Number>> gathered_qp_data(n_vars);
1958 for (auto var : index_range(gathered_qp_data))
1959 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.
1966 {
1967 // Clear any data from previous bf
1968 for (auto var : index_range(gathered_qp_data))
1969 gathered_qp_data[var].clear();
1970
1971 for (const auto & pr : _local_eim_basis_functions[bf])
1972 {
1973 // array[n_vars][n_qp] per Elem
1974 const auto & array = pr.second;
1975 for (auto var : index_range(array))
1976 {
1977 // Insert all qp values for this var
1978 gathered_qp_data[var].insert(/*insert at*/gathered_qp_data[var].end(),
1979 /*data start*/array[var].begin(),
1980 /*data end*/array[var].end());
1981 }
1982 }
1983
1984 // Reference to the data map for the current basis function.
1985 auto & bf_map = _local_eim_basis_functions[bf];
1986
1987 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 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 if (this->processor_id() == 0)
2000 {
2001 auto cursor = gathered_qp_data[var].begin();
2002 for (auto i : index_range(elem_ids))
2003 {
2004 auto elem_id = elem_ids[i];
2005 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 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 if (array.empty())
2016 array.resize(n_vars);
2017
2018 array[var].assign(cursor, cursor + n_qp_this_elem);
2019 std::advance(cursor, n_qp_this_elem);
2020 }
2021 }
2022 }
2023 } // end loop over basis functions
2024}
2025
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 auto n_procs = this->n_processors();
2041
2042 // In serial there's nothing to gather
2043 if (n_procs == 1)
2044 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 auto n_bf = _local_side_eim_basis_functions.size();
2050 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 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 _local_side_eim_basis_functions[0].empty() ? 0 : _local_side_eim_basis_functions[0].begin()->second.size();
2064 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 std::vector<std::pair<dof_id_type,unsigned int>> elem_side_pairs;
2070 elem_side_pairs.reserve(_local_side_eim_basis_functions[0].size());
2071 for (const auto & pr : _local_side_eim_basis_functions[0])
2072 elem_side_pairs.push_back(pr.first);
2073 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 std::vector<unsigned int> n_qp_per_elem_side;
2079 n_qp_per_elem_side.reserve(_local_side_eim_basis_functions[0].size());
2080 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 const auto & array = pr.second;
2085 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 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 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 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 std::vector<std::vector<Number>> gathered_qp_data(n_vars);
2106 for (auto var : index_range(gathered_qp_data))
2107 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.
2114 {
2115 // Clear any data from previous bf
2116 for (auto var : index_range(gathered_qp_data))
2117 gathered_qp_data[var].clear();
2118
2119 for (const auto & pr : _local_side_eim_basis_functions[bf])
2120 {
2121 // array[n_vars][n_qp] per (elem,side) pair
2122 const auto & array = pr.second;
2123 for (auto var : index_range(array))
2124 {
2125 // Insert all qp values for this var
2126 gathered_qp_data[var].insert(/*insert at*/gathered_qp_data[var].end(),
2127 /*data start*/array[var].begin(),
2128 /*data end*/array[var].end());
2129 }
2130 }
2131
2132 // Reference to the data map for the current basis function.
2133 auto & bf_map = _local_side_eim_basis_functions[bf];
2134
2135 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 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 if (this->processor_id() == 0)
2148 {
2149 auto cursor = gathered_qp_data[var].begin();
2150 for (auto i : index_range(elem_side_pairs))
2151 {
2152 auto elem_side_pair = elem_side_pairs[i];
2153 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 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 if (array.empty())
2164 array.resize(n_vars);
2165
2166 array[var].assign(cursor, cursor + n_qp_this_elem_side);
2167 std::advance(cursor, n_qp_this_elem_side);
2168 }
2169 }
2170 }
2171 } // end loop over basis functions
2172}
2173
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 auto n_procs = this->n_processors();
2189
2190 // In serial there's nothing to gather
2191 if (n_procs == 1)
2192 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 auto n_bf = _local_node_eim_basis_functions.size();
2198 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 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 _local_node_eim_basis_functions[0].empty() ? 0 : _local_node_eim_basis_functions[0].begin()->second.size();
2212 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 unsigned int n_local_nodes = _local_node_eim_basis_functions[0].size();
2218 std::vector<dof_id_type> node_ids;
2219 node_ids.reserve(n_local_nodes);
2220 for (const auto & pr : _local_node_eim_basis_functions[0])
2221 node_ids.push_back(pr.first);
2222 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 std::vector<std::vector<Number>> gathered_node_data(n_vars);
2230 {
2231 // Clear any data from previous bf
2232 for (auto var : index_range(gathered_node_data))
2233 {
2234 gathered_node_data[var].clear();
2235 gathered_node_data[var].resize(n_local_nodes);
2236 }
2237
2238 unsigned int local_node_idx = 0;
2239 for (const auto & pr : _local_node_eim_basis_functions[bf])
2240 {
2241 // array[n_vars] per Node
2242 const auto & array = pr.second;
2243 for (auto var : index_range(array))
2244 {
2245 gathered_node_data[var][local_node_idx] = array[var];
2246 }
2247
2248 local_node_idx++;
2249 }
2250
2251 // Reference to the data map for the current basis function.
2252 auto & bf_map = _local_node_eim_basis_functions[bf];
2253
2254 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 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 if (this->processor_id() == 0)
2267 {
2268 auto cursor = gathered_node_data[var].begin();
2269 for (auto i : index_range(node_ids))
2270 {
2271 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 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 if (array.empty())
2282 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 array[var] = *cursor;
2288 std::advance(cursor, 1);
2289 }
2290 }
2291 }
2292 } // end loop over basis functions
2293}
2294
2295
2296
2298{
2299 // So we can avoid calling these many times below
2300 auto n_procs = sys.comm().size();
2301 auto rank = sys.comm().rank();
2302
2303 // In serial there's nothing to distribute
2304 if (n_procs == 1)
2305 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 auto n_bf = _local_eim_basis_functions.size();
2311 sys.comm().broadcast(n_bf);
2312
2313 // Allocate enough space to store n_bf basis functions on non-zero ranks
2314 if (rank != 0)
2315 _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 auto n_vars = _local_eim_basis_functions[0].begin()->second.size();
2320 sys.comm().broadcast(n_vars);
2321
2322 // Construct lists of elem ids owned by different processors
2323 const MeshBase & mesh = sys.get_mesh();
2324
2325 std::vector<dof_id_type> gathered_local_elem_ids;
2326 gathered_local_elem_ids.reserve(mesh.n_elem());
2327 for (const auto & elem : mesh.active_local_element_ptr_range())
2328 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 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 auto n_local_elems = gathered_local_elem_ids.size();
2338 std::vector<std::size_t> gathered_n_local_elems = {n_local_elems};
2339 sys.comm().gather(/*root_id=*/0, gathered_n_local_elems);
2340
2341 // Gather the elem ids owned by each processor onto processor 0.
2342 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 std::vector<std::size_t> start_elem_ids_index, end_elem_ids_index;
2348
2349 if (rank == 0)
2350 {
2351 start_elem_ids_index.resize(n_procs);
2352 start_elem_ids_index[0] = 0;
2353 for (processor_id_type p=1; p<n_procs; ++p)
2354 start_elem_ids_index[p] = start_elem_ids_index[p-1] + gathered_n_local_elems[p-1];
2355
2356 end_elem_ids_index.resize(n_procs);
2357 end_elem_ids_index[n_procs - 1] = gathered_local_elem_ids.size();
2358 for (processor_id_type p=0; p<n_procs - 1; ++p)
2359 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 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 std::vector<int> counts;
2372
2373 if (rank == 0)
2374 {
2375 n_qp_per_elem_data.reserve(gathered_local_elem_ids.size());
2376 counts.resize(n_procs);
2377
2378 auto & bf_map = _local_eim_basis_functions[0];
2379
2380 for (processor_id_type p=0; p<n_procs; ++p)
2381 {
2382 for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2383 {
2384 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 const auto & array = libmesh_map_find(bf_map, elem_id);
2389
2390 auto n_qps = array[0].size();
2391
2392 // We use var==0 to set the number of qps for all vars
2393 n_qp_per_elem_data.push_back(n_qps);
2394
2395 // Accumulate the count for this proc
2396 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 std::vector<unsigned int> recv;
2405 std::vector<int> tmp(gathered_n_local_elems.begin(), gathered_n_local_elems.end());
2406 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 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 std::vector<std::vector<Number>> qp_data(n_vars);
2420 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 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 for (auto var : index_range(qp_data))
2430 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 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 for (auto bf : make_range(n_bf))
2440 {
2441 // Prepare data for scattering (only on proc 0)
2442 if (rank == 0)
2443 {
2444 // Reference to the data map for the current basis function.
2445 auto & bf_map = _local_eim_basis_functions[bf];
2446
2447 // Clear any data from previous bf
2448 for (auto var : index_range(qp_data))
2449 qp_data[var].clear();
2450
2451 for (processor_id_type p=0; p<n_procs; ++p)
2452 {
2453 for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2454 {
2455 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 const auto & array = libmesh_map_find(bf_map, elem_id);
2460
2461 for (auto var : index_range(array))
2462 {
2463 // Insert all qp values for this var
2464 qp_data[var].insert(/*insert at*/qp_data[var].end(),
2465 /*data start*/array[var].begin(),
2466 /*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 for (auto var : make_range(n_vars))
2474 {
2475 // Do the scatter for the current var
2476 sys.comm().scatter(qp_data[var], counts, recv_qp_data, /*root_id=*/0);
2477
2478 if (rank != 0)
2479 {
2480 // Store the scattered data we received in _local_eim_basis_functions[bf]
2481 auto & bf_map = _local_eim_basis_functions[bf];
2482 auto cursor = recv_qp_data.begin();
2483
2484 for (auto i : index_range(gathered_local_elem_ids))
2485 {
2486 auto elem_id = gathered_local_elem_ids[i];
2487 auto n_qp_this_elem = n_qp_per_elem_data[i];
2488 auto & array = bf_map[elem_id];
2489
2490 // Create space to store the data if it doesn't already exist.
2491 if (array.empty())
2492 array.resize(n_vars);
2493
2494 array[var].assign(cursor, cursor + n_qp_this_elem);
2495 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 if (rank == 0)
2505 {
2506 for (processor_id_type p=1; p<n_procs; ++p)
2507 {
2508 for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2509 {
2510 auto elem_id = gathered_local_elem_ids[e];
2511
2512 // Delete this Elem's information from every basis function.
2513 for (auto & bf_map : _local_eim_basis_functions)
2514 bf_map.erase(elem_id);
2515 } // end for (e)
2516 } // end for proc_id
2517 } // if (rank == 0)
2518}
2519
2521{
2522 // So we can avoid calling these many times below
2523 auto n_procs = sys.comm().size();
2524 auto rank = sys.comm().rank();
2525
2526 // In serial there's nothing to distribute
2527 if (n_procs == 1)
2528 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 auto n_bf = _local_side_eim_basis_functions.size();
2534 sys.comm().broadcast(n_bf);
2535
2536 // Allocate enough space to store n_bf basis functions on non-zero ranks
2537 if (rank != 0)
2539
2540 // Broadcast the number of variables from proc 0. After
2541 // distributing, all procs should have the same number of variables.
2542 auto n_vars = _local_side_eim_basis_functions[0].begin()->second.size();
2543 sys.comm().broadcast(n_vars);
2544
2545 const std::set<boundary_id_type> & parametrized_function_boundary_ids =
2547
2548 // Construct lists of elem ids owned by different processors
2549 const MeshBase & mesh = sys.get_mesh();
2550
2551 // BoundaryInfo and related data structures
2552 const auto & binfo = mesh.get_boundary_info();
2553 std::vector<boundary_id_type> side_boundary_ids;
2554
2555 std::vector<dof_id_type> gathered_local_elem_ids;
2556 std::vector<dof_id_type> gathered_local_side_indices;
2557 for (const auto & elem : mesh.active_local_element_ptr_range())
2558 {
2559 for (unsigned int side = 0; side != elem->n_sides(); ++side)
2560 {
2561 // skip non-boundary elements
2562 if (!elem->neighbor_ptr(side))
2563 {
2564 binfo.boundary_ids(elem, side, side_boundary_ids);
2565
2566 bool has_side_boundary_id = false;
2567 for (boundary_id_type side_boundary_id : side_boundary_ids)
2568 if (parametrized_function_boundary_ids.count(side_boundary_id))
2569 {
2570 has_side_boundary_id = true;
2571 break;
2572 }
2573
2574 if (has_side_boundary_id)
2575 {
2576 gathered_local_elem_ids.push_back(elem->id());
2577 gathered_local_side_indices.push_back(side);
2578 }
2579 }
2580 }
2581
2582 // In the case of 2D elements, we also check the shellfaces
2583 if (elem->dim() == 2)
2584 for (unsigned int shellface_index=0; shellface_index<2; shellface_index++)
2585 {
2586 binfo.shellface_boundary_ids(elem, shellface_index, side_boundary_ids);
2587
2588 bool has_side_boundary_id = false;
2589 for (boundary_id_type side_boundary_id : side_boundary_ids)
2590 if (parametrized_function_boundary_ids.count(side_boundary_id))
2591 {
2592 has_side_boundary_id = true;
2593 break;
2594 }
2595
2596 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 gathered_local_elem_ids.push_back(elem->id());
2601 gathered_local_side_indices.push_back(shellface_index);
2602 }
2603 }
2604 }
2605
2606 // Gather the number of local elems from all procs to proc 0
2607 auto n_local_elems = gathered_local_elem_ids.size();
2608 std::vector<std::size_t> gathered_n_local_elems = {n_local_elems};
2609 sys.comm().gather(/*root_id=*/0, gathered_n_local_elems);
2610
2611 // Gather the (elem,side) owned by each processor onto processor 0.
2612 sys.comm().gather(/*root_id=*/0, gathered_local_elem_ids);
2613 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 std::vector<std::size_t> start_elem_ids_index, end_elem_ids_index;
2619
2620 if (rank == 0)
2621 {
2622 start_elem_ids_index.resize(n_procs);
2623 start_elem_ids_index[0] = 0;
2624 for (processor_id_type p=1; p<n_procs; ++p)
2625 start_elem_ids_index[p] = start_elem_ids_index[p-1] + gathered_n_local_elems[p-1];
2626
2627 end_elem_ids_index.resize(n_procs);
2628 end_elem_ids_index[n_procs - 1] = gathered_local_elem_ids.size();
2629 for (processor_id_type p=0; p<n_procs - 1; ++p)
2630 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 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 std::vector<int> counts;
2643
2644 if (rank == 0)
2645 {
2646 n_qp_per_elem_data.reserve(gathered_local_elem_ids.size());
2647 counts.resize(n_procs);
2648
2649 auto & bf_map = _local_side_eim_basis_functions[0];
2650
2651 for (processor_id_type p=0; p<n_procs; ++p)
2652 {
2653 for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2654 {
2655 auto elem_id = gathered_local_elem_ids[e];
2656 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 const auto & array = libmesh_map_find(bf_map, std::make_pair(elem_id, side_index));
2661
2662 auto n_qps = array[0].size();
2663
2664 // We use var==0 to set the number of qps for all vars
2665 n_qp_per_elem_data.push_back(n_qps);
2666
2667 // Accumulate the count for this proc
2668 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 std::vector<unsigned int> recv;
2677 std::vector<int> tmp(gathered_n_local_elems.begin(), gathered_n_local_elems.end());
2678 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 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 std::vector<std::vector<Number>> qp_data(n_vars);
2692 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 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 for (auto var : index_range(qp_data))
2702 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 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 for (auto bf : make_range(n_bf))
2712 {
2713 // Prepare data for scattering (only on proc 0)
2714 if (rank == 0)
2715 {
2716 // Reference to the data map for the current basis function.
2717 auto & bf_map = _local_side_eim_basis_functions[bf];
2718
2719 // Clear any data from previous bf
2720 for (auto var : index_range(qp_data))
2721 qp_data[var].clear();
2722
2723 for (processor_id_type p=0; p<n_procs; ++p)
2724 {
2725 for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2726 {
2727 auto elem_id = gathered_local_elem_ids[e];
2728 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 const auto & array = libmesh_map_find(bf_map, std::make_pair(elem_id, side_index));
2733
2734 for (auto var : index_range(array))
2735 {
2736 // Insert all qp values for this var
2737 qp_data[var].insert(/*insert at*/qp_data[var].end(),
2738 /*data start*/array[var].begin(),
2739 /*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 for (auto var : make_range(n_vars))
2747 {
2748 // Do the scatter for the current var
2749 sys.comm().scatter(qp_data[var], counts, recv_qp_data, /*root_id=*/0);
2750
2751 if (rank != 0)
2752 {
2753 // Store the scattered data we received in _local_side_eim_basis_functions[bf]
2754 auto & bf_map = _local_side_eim_basis_functions[bf];
2755 auto cursor = recv_qp_data.begin();
2756
2757 for (auto i : index_range(gathered_local_elem_ids))
2758 {
2759 auto elem_id = gathered_local_elem_ids[i];
2760 auto side_index = gathered_local_side_indices[i];
2761 auto n_qp_this_elem = n_qp_per_elem_data[i];
2762 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 if (array.empty())
2766 array.resize(n_vars);
2767
2768 array[var].assign(cursor, cursor + n_qp_this_elem);
2769 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 if (rank == 0)
2779 {
2780 for (processor_id_type p=1; p<n_procs; ++p)
2781 {
2782 for (auto e : make_range(start_elem_ids_index[p], end_elem_ids_index[p]))
2783 {
2784 auto elem_id = gathered_local_elem_ids[e];
2785 auto side_index = gathered_local_side_indices[e];
2786
2787 // Delete this Elem's information from every basis function.
2788 for (auto & bf_map : _local_side_eim_basis_functions)
2789 bf_map.erase(std::make_pair(elem_id, side_index));
2790 } // end for (e)
2791 } // end for proc_id
2792 } // if (rank == 0)
2793}
2794
2796{
2797 // So we can avoid calling these many times below
2798 auto n_procs = sys.comm().size();
2799 auto rank = sys.comm().rank();
2800
2801 // In serial there's nothing to distribute
2802 if (n_procs == 1)
2803 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 auto n_bf = _local_node_eim_basis_functions.size();
2809 sys.comm().broadcast(n_bf);
2810
2811 // Allocate enough space to store n_bf basis functions on non-zero ranks
2812 if (rank != 0)
2814
2815 // Broadcast the number of variables from proc 0. After
2816 // distributing, all procs should have the same number of variables.
2817 auto n_vars = _local_node_eim_basis_functions[0].begin()->second.size();
2818 sys.comm().broadcast(n_vars);
2819
2820 // Construct lists of elem ids owned by different processors
2821 const MeshBase & mesh = sys.get_mesh();
2822
2823 std::vector<dof_id_type> gathered_local_node_ids;
2824 {
2825 const std::set<boundary_id_type> & parametrized_function_boundary_ids =
2827
2828 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 std::set<dof_id_type> nodes_with_nodesets;
2835 for (const auto & t : binfo.build_node_list())
2836 nodes_with_nodesets.insert(std::get<0>(t));
2837
2838 // To be filled in by BoundaryInfo calls in loop below
2839 std::vector<boundary_id_type> node_boundary_ids;
2840
2841 for(dof_id_type node_id : nodes_with_nodesets)
2842 {
2843 const Node * node = mesh.node_ptr(node_id);
2844
2845 if (node->processor_id() != mesh.comm().rank())
2846 continue;
2847
2848 binfo.boundary_ids(node, node_boundary_ids);
2849
2850 bool has_node_boundary_id = false;
2851 for(boundary_id_type node_boundary_id : node_boundary_ids)
2852 if(parametrized_function_boundary_ids.count(node_boundary_id))
2853 {
2854 has_node_boundary_id = true;
2855 break;
2856 }
2857
2858 if(has_node_boundary_id)
2859 {
2860 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 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 auto n_local_nodes = gathered_local_node_ids.size();
2873 std::vector<std::size_t> gathered_n_local_nodes = {n_local_nodes};
2874 sys.comm().gather(/*root_id=*/0, gathered_n_local_nodes);
2875
2876 // Gather the node ids owned by each processor onto processor 0.
2877 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 std::vector<std::size_t> start_node_ids_index, end_node_ids_index;
2883
2884 if (rank == 0)
2885 {
2886 start_node_ids_index.resize(n_procs);
2887 start_node_ids_index[0] = 0;
2888 for (processor_id_type p=1; p<n_procs; ++p)
2889 start_node_ids_index[p] = start_node_ids_index[p-1] + gathered_n_local_nodes[p-1];
2890
2891 end_node_ids_index.resize(n_procs);
2892 end_node_ids_index[n_procs - 1] = gathered_local_node_ids.size();
2893 for (processor_id_type p=0; p<n_procs - 1; ++p)
2894 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 std::vector<int> counts;
2906
2907 if (rank == 0)
2908 {
2909 counts.resize(n_procs);
2910
2911 for (processor_id_type p=0; p<n_procs; ++p)
2912 {
2913 auto node_ids_range = (end_node_ids_index[p] - start_node_ids_index[p]);
2914
2915 // Accumulate the count for this proc
2916 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 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 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 int count_sum = std::accumulate(counts.begin(), counts.end(), 0);
2933 for (auto var : index_range(node_data))
2934 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 for (auto bf : make_range(n_bf))
2939 {
2940 // Prepare data for scattering (only on proc 0)
2941 if (rank == 0)
2942 {
2943 // Reference to the data map for the current basis function.
2944 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 for (auto var : index_range(node_data))
2949 node_data[var].clear();
2950
2951 for (processor_id_type p=0; p<n_procs; ++p)
2952 {
2953 for (auto n : make_range(start_node_ids_index[p], end_node_ids_index[p]))
2954 {
2955 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 const auto & array = libmesh_map_find(bf_map, node_id);
2960
2961 for (auto var : index_range(array))
2962 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 for (auto var : make_range(n_vars))
2969 {
2970 // Do the scatter for the current var
2971 sys.comm().scatter(node_data[var], counts, recv_node_data, /*root_id=*/0);
2972
2973 if (rank != 0)
2974 {
2975 // Store the scattered data we received in _local_eim_basis_functions[bf]
2976 auto & bf_map = _local_node_eim_basis_functions[bf];
2977 auto cursor = recv_node_data.begin();
2978
2979 for (auto i : index_range(gathered_local_node_ids))
2980 {
2981 auto node_id = gathered_local_node_ids[i];
2982 auto & array = bf_map[node_id];
2983
2984 // Create space to store the data if it doesn't already exist.
2985 if (array.empty())
2986 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 array[var] = *cursor;
2992 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 if (rank == 0)
3002 {
3003 for (processor_id_type p=1; p<n_procs; ++p)
3004 {
3005 for (auto n : make_range(start_node_ids_index[p], end_node_ids_index[p]))
3006 {
3007 auto node_id = gathered_local_node_ids[n];
3008
3009 // Delete this Node's information from every basis function.
3010 for (auto & bf_map : _local_node_eim_basis_functions)
3011 bf_map.erase(node_id);
3012 } // end for (n)
3013 } // end for proc_id
3014 } // if (rank == 0)
3015}
3016
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}
3024
3025const std::vector<EIMVarGroupPlottingInfo> & RBEIMEvaluation::get_eim_vars_to_project_and_write() const
3026{
3028}
3029
3030const std::set<unsigned int> & RBEIMEvaluation::scale_components_in_enrichment() const
3031{
3033}
3034
3036{
3037 // Return false by default, but we override this in subclasses
3038 // for cases where we want to use the error indicator.
3039 return false;
3040}
3041
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.
3051}
3052
3054 Number error_indicator_rhs,
3055 const DenseVector<Number> & eim_solution,
3056 const DenseVector<Number> & eim_rhs)
3057{
3058 DenseVector<Number> coeffs;
3060
3061 Number EIM_val_at_error_indicator_pt = coeffs.dot(eim_solution);
3062 Real error_indicator_val =
3063 std::real(error_indicator_rhs - EIM_val_at_error_indicator_pt);
3064
3065 Real normalization = 0.;
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 normalization =
3073 std::abs(error_indicator_rhs) + std::abs(EIM_val_at_error_indicator_pt);
3074 }
3076 {
3077 // Normalize with respect to the right-hand side from the "EIM residual" calculation.
3078 normalization = std::abs(error_indicator_rhs);
3079 }
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 normalization = std::max(eim_rhs.linfty_norm(), std::abs(error_indicator_rhs));
3090 }
3091 else
3092 {
3093 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 Real orig_normalization = normalization;
3101 if (normalization == 0.)
3102 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 Real rel_error_indicator = std::abs(error_indicator_val) / normalization;
3111 if (limit_eim_error_indicator_to_one && (rel_error_indicator > 1.0))
3112 rel_error_indicator = 1.0;
3113
3114 return std::make_pair(rel_error_indicator, orig_normalization);
3115}
3116
3121
3123{
3124 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 for (const auto & [key, val] : rb_property_map)
3129}
3130
3131const DenseVector<Number> & RBEIMEvaluation::get_error_indicator_interpolation_row() const
3132{
3133 return _error_indicator_interpolation_row;
3134}
3135
3136void RBEIMEvaluation::set_error_indicator_interpolation_row(const DenseVector<Number> & extra_point_row)
3137{
3138 _error_indicator_interpolation_row = extra_point_row;
3139}
3140
3141} // namespace libMesh
unsigned int n_vars
void max(const T &r, T &o, Request &req) const
processor_id_type size() const
processor_id_type rank() const
void scatter(const std::vector< T, A > &data, T &recv, const unsigned int root_id=0) const
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
void gather(const unsigned int root_id, const T &send_data, std::vector< T, A > &recv) const
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
Fills a user-provided std::vector with the boundary ids associated with Node node.
Defines a dense matrix for use in Finite Element-type computations.
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
void get_principal_submatrix(unsigned int sub_m, unsigned int sub_n, DenseMatrix< T > &dest) const
Put the sub_m x sub_n principal submatrix into dest.
Defines a dense vector for use in Finite Element-type computations.
Real linfty_norm() const
void get_principal_subvector(unsigned int sub_n, DenseVector< T > &dest) const
Puts the principal subvector of size sub_n (i.e.
CompareTypes< T, T2 >::supertype dot(const DenseVector< T2 > &vec) const
virtual unsigned int size() const override final
processor_id_type processor_id() const
Definition dof_object.h:881
This is the MeshBase class.
Definition mesh_base.h:81
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
A Node is like a Point, but with more information.
Definition node.h:55
An object whose state is distributed along a set of processors.
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
processor_id_type n_processors() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
This class enables evaluation of an Empirical Interpolation Method (EIM) approximation.
const SideQpDataMap & get_side_basis_function(unsigned int i) const
Get a reference to the i^th side basis function.
void distribute_bfs(const System &sys)
Helper function that distributes the entries of _local_eim_basis_functions to their respective proces...
Point get_interpolation_points_xyz(unsigned int index) const
Get the data associated with EIM interpolation points.
virtual void project_qp_data_vector_onto_system(System &sys, const std::vector< Number > &bf_data, const EIMVarGroupPlottingInfo &eim_vargroup, const std::map< std::string, std::string > &extra_options)
Project the EIM basis function data stored in bf_data onto sys.solution.
void add_interpolation_data(Point p, unsigned int comp, dof_id_type elem_id, subdomain_id_type subdomain_id, unsigned int qp, const std::vector< Point > &perturbs, const std::vector< Real > &phi_i_qp, ElemType elem_type, const std::vector< Real > &JxW_all_qp, const std::vector< std::vector< Real > > &phi_i_all_qp, Order qrule_order, const Point &dxyz_dxi_elem_center, const Point &dxyz_deta_elem_center)
Add interpolation data associated with a new basis function.
void add_interpolation_points_subdomain_id(subdomain_id_type sbd_id)
void add_interpolation_points_comp(unsigned int comp)
void add_basis_function(const QpDataMap &bf)
Add bf to our EIM basis.
virtual bool use_eim_error_indicator() const
Virtual function to indicate if we use the EIM error indicator in this case.
bool _preserve_rb_eim_solutions
Boolean to indicate if we skip updating _rb_eim_solutions in rb_eim_solves().
void add_interpolation_points_phi_i_all_qp(const std::vector< std::vector< Real > > &phi_i_all_qp)
void add_node_interpolation_data(Point p, unsigned int comp, dof_id_type node_id, boundary_id_type boundary_id)
Add interpolation data associated with a new basis function.
void read_in_interior_basis_functions(const System &sys, const std::string &directory_name, bool read_binary_basis_functions)
Method that reads in element interior EIM basis functions.
void read_in_node_basis_functions(const System &sys, const std::string &directory_name, bool read_binary_basis_functions)
Method that reads in element node EIM basis functions.
void add_rb_property_map_entry(std::string &property_name, std::set< dof_id_type > &entity_ids)
DenseMatrix< Number > _interpolation_matrix
Dense matrix that stores the lower triangular interpolation matrix that can be used.
std::vector< std::vector< std::vector< Number > > > get_interior_basis_functions_as_vecs()
Get all interior basis functions in the form of std::vectors.
std::vector< DenseVector< Number > > _eim_solutions_for_training_set
Storage for EIM solutions from the training set.
void add_interpolation_points_xyz_perturbations(const std::vector< Point > &perturbs)
const std::unordered_map< std::string, std::set< dof_id_type > > & get_rb_property_map() const
void decrement_vector(QpDataMap &v, const DenseVector< Number > &coeffs)
Subtract coeffs[i]*basis_function[i] from v.
void print_local_eim_basis_functions() const
Print the contents of _local_eim_basis_functions to libMesh::out.
unsigned int get_interpolation_points_side_index(unsigned int index) const
const std::map< dof_id_type, unsigned int > & get_elem_id_to_local_index_map() const
std::vector< QpDataMap > _local_eim_basis_functions
The EIM basis functions.
unsigned int get_n_elems() const
Return the number of unique elements containing interpolation points.
std::vector< unsigned int > _interpolation_points_comp
In the case of a "vector-valued" EIM, this vector determines which component of the parameterized fun...
EimErrorIndicatorNormalization eim_error_indicator_normalization
std::vector< std::pair< Real, Real > > _rb_eim_error_indicators
If we're using the EIM error indicator, then we store the error indicator values corresponding to _rb...
const DenseMatrix< Number > & get_interpolation_matrix() const
Get the EIM interpolation matrix.
void add_interpolation_points_side_index(unsigned int side_index)
void side_gather_bfs()
Same as gather_bfs() except for side data.
const std::set< unsigned int > & scale_components_in_enrichment() const
Get _scale_components_in_enrichment.
Order get_interpolation_points_qrule_order(unsigned int index) const
virtual std::unique_ptr< RBTheta > build_eim_theta(unsigned int index)
Build a theta object corresponding to EIM index index.
void add_interpolation_points_elem_type(ElemType elem_type)
Number get_eim_basis_function_node_local_value(unsigned int basis_function_index, dof_id_type node_id, unsigned int var) const
Same as get_eim_basis_function_values_at_qps() except for node data.
void add_interpolation_points_boundary_id(boundary_id_type b_id)
Number get_eim_basis_function_node_value(unsigned int basis_function_index, dof_id_type node_id, unsigned int var) const
Same as get_eim_basis_function_value() except for node data.
void set_preserve_rb_eim_solutions(bool preserve_rb_eim_solutions)
Set _preserve_rb_eim_solutions.
std::pair< Real, Real > get_eim_error_indicator(Number error_indicator_rhs, const DenseVector< Number > &eim_solution, const DenseVector< Number > &eim_rhs)
Evaluates the EIM error indicator based on error_indicator_rhs, eim_solution, and _error_indicator_in...
std::vector< std::vector< unsigned int > > _interpolation_points_spatial_indices
Here we store the spatial indices that were initialized by initialize_spatial_indices_at_interp_pts()...
void set_rb_eim_solutions(const std::vector< DenseVector< Number > > &rb_eim_solutions)
Set _rb_eim_solutions.
unsigned int get_n_basis_functions() const
Return the current number of EIM basis functions.
void add_interpolation_points_qp(unsigned int qp)
void write_out_interior_basis_functions(const std::string &directory_name, bool write_binary_basis_functions)
Method that writes out element interior EIM basis functions.
void add_interpolation_points_elem_id(dof_id_type elem_id)
subdomain_id_type get_interpolation_points_subdomain_id(unsigned int index) const
unsigned int get_n_interpolation_points() const
Return the number of interpolation points.
std::vector< NodeDataMap > _local_node_eim_basis_functions
The EIM basis functions on element nodes (e.g.
Number get_eim_basis_function_value(unsigned int basis_function_index, dof_id_type elem_id, unsigned int comp, unsigned int qp) const
Same as above, except that we just return the value at the qp^th quadrature point.
unsigned int get_interpolation_points_comp(unsigned int index) const
unsigned int get_n_properties() const
Return the number of properties stored in the rb_property_map.
void write_out_node_basis_functions(const std::string &directory_name, bool write_binary_basis_functions)
Method that writes out element node EIM basis functions.
void side_distribute_bfs(const System &sys)
Same as distribute_bfs() except for side data.
void initialize_eim_theta_objects()
Build a vector of RBTheta objects that accesses the components of the RB_solution member variable of ...
const std::vector< Real > & get_interpolation_points_JxW_all_qp(unsigned int index) const
void set_n_basis_functions(unsigned int n_bfs)
Set the number of basis functions.
std::vector< std::unique_ptr< RBTheta > > _rb_eim_theta_objects
The vector of RBTheta objects that are created to point to this RBEIMEvaluation.
const std::vector< std::vector< Real > > & get_interpolation_points_phi_i_all_qp(unsigned int index) const
std::vector< SideQpDataMap > _local_side_eim_basis_functions
The EIM basis functions on element sides.
const QpDataMap & get_basis_function(unsigned int i) const
Get a reference to the i^th basis function.
void node_distribute_bfs(const System &sys)
Same as distribute_bfs() except for node data.
void add_interpolation_points_xyz(Point p)
Set the data associated with EIM interpolation points.
bool _is_eim_error_indicator_active
Indicate if the EIM error indicator is active in RB EIM solves.
void read_in_basis_functions(const System &sys, const std::string &directory_name="offline_data", bool read_binary_basis_functions=true)
Read in all the basis functions from file.
void add_interpolation_points_phi_i_qp(const std::vector< Real > &phi_i_qp)
void rb_eim_solves(const std::vector< RBParameters > &mus, unsigned int N)
Perform rb_eim_solves at each mu in mus and store the results in _rb_eim_solutions.
static void get_parametrized_function_side_values_at_qps(const SideQpDataMap &pf, dof_id_type elem_id, unsigned int side_index, unsigned int comp, std::vector< Number > &values)
Same as get_parametrized_function_values_at_qps() except for side data.
virtual void clear() override
Clear this object.
void add_node_basis_function(const NodeDataMap &node_bf)
Add node_bf to our EIM basis.
std::set< unsigned int > _scale_components_in_enrichment
This set that specifies which EIM variables will be scaled during EIM enrichment so that their maximu...
std::vector< Number > get_rb_eim_solutions_entries(unsigned int index) const
Return entry index for each solution in _rb_eim_solutions.
void resize_data_structures(const unsigned int Nmax)
Resize the data structures for storing data associated with this object.
void set_parametrized_function(std::unique_ptr< RBParametrizedFunction > pf)
Set the parametrized function that we will approximate using the Empirical Interpolation Method.
dof_id_type get_interpolation_points_elem_id(unsigned int index) const
void add_interpolation_points_spatial_indices(const std::vector< unsigned int > &spatial_indices)
void side_decrement_vector(SideQpDataMap &v, const DenseVector< Number > &coeffs)
Same as decrement_vector() except for Side data.
const NodeDataMap & get_node_basis_function(unsigned int i) const
Get a reference to the i^th node basis function.
void read_in_side_basis_functions(const System &sys, const std::string &directory_name, bool read_binary_basis_functions)
Method that reads in element side EIM basis functions.
void set_eim_error_indicator_active(bool is_active)
Activate/decative the error indicator in EIM solves.
void initialize_rb_property_map()
Initialize the rb_property_map of RBEIMEvaluation (this) from the rb_property_map stored in RBParamet...
const std::vector< Real > & get_interpolation_points_phi_i_qp(unsigned int index) const
void add_side_basis_function(const SideQpDataMap &side_bf)
Add side_bf to our EIM basis.
std::map< std::pair< dof_id_type, unsigned int >, std::vector< std::vector< Number > > > SideQpDataMap
Type of the data structure used to map from (elem id, side index) -> [n_vars][n_qp] data.
const std::vector< EIMVarGroupPlottingInfo > & get_eim_vars_to_project_and_write() const
Get _eim_vars_to_project_and_write.
void initialize_interpolation_points_spatial_indices()
Initialize _interpolation_points_spatial_indices.
dof_id_type get_interpolation_points_node_id(unsigned int index) const
const std::vector< unsigned int > & get_interpolation_points_spatial_indices(unsigned int index) const
unsigned int get_n_interpolation_points_spatial_indices() const
_interpolation_points_spatial_indices is optional data, so we need to be able to check how many _inte...
Number get_eim_basis_function_side_value(unsigned int basis_function_index, dof_id_type elem_id, unsigned int side_index, unsigned int comp, unsigned int qp) const
Same as get_eim_basis_function_value() except for side data.
void write_out_basis_functions(const std::string &directory_name="offline_data", bool write_binary_basis_functions=true)
Write out all the basis functions to file.
std::vector< std::vector< Number > > get_interior_basis_function_as_vec_helper(unsigned int n_vars, unsigned int n_qp_data, unsigned int bf_index)
Helper function called by write_out_interior_basis_functions() to get basis function bf_index stored ...
static Number get_parametrized_function_node_local_value(const NodeDataMap &pf, dof_id_type node_id, unsigned int comp)
Same as get_parametrized_function_values_at_qps() except for node data.
const std::vector< DenseVector< Number > > & get_rb_eim_solutions() const
Return the EIM solution coefficients from the most recent call to rb_eim_solves().
void add_elem_id_local_index_map_entry(dof_id_type elem_id, unsigned int local_index)
void set_interpolation_matrix_entry(unsigned int i, unsigned int j, Number value)
Set entry of the EIM interpolation matrix.
std::map< std::string, std::size_t > get_interior_basis_function_sizes(std::vector< unsigned int > &n_qp_per_elem)
Get data that defines the sizes of interior EIM basis functions.
const std::vector< DenseVector< Number > > & get_eim_solutions_for_training_set() const
Return a const reference to the EIM solutions for the parameters in the training set.
DenseVector< Number > _error_indicator_interpolation_row
Here we store an extra row of the interpolation matrix which is used to compute the EIM error indicat...
std::vector< RBParameters > _rb_eim_solves_mus
The parameters and the number of basis functions that were used in the most recent call to rb_eim_sol...
VectorizedEvalInput _vec_eval_input
We store the EIM interpolation point data in this object.
static Number get_parametrized_function_side_value(const Parallel::Communicator &comm, const SideQpDataMap &pf, dof_id_type elem_id, unsigned int side_index, unsigned int comp, unsigned int qp)
Same as get_parametrized_function_value() except for side data.
void node_decrement_vector(NodeDataMap &v, const DenseVector< Number > &coeffs)
Same as decrement_vector() except for node data.
void add_interpolation_points_qrule_order(Order qrule_order)
static void get_parametrized_function_values_at_qps(const QpDataMap &pf, dof_id_type elem_id, unsigned int comp, std::vector< Number > &values)
Fill up values by evaluating the parametrized function pf for all quadrature points on element elem_i...
void add_elem_center_dxyzdxi(const Point &dxyzdxi)
void node_gather_bfs()
Same as gather_bfs() except for node data.
bool limit_eim_error_indicator_to_one
If this boolean is true then we clamp EIM error indicator values to be at most 1.
void add_elem_center_dxyzdeta(const Point &dxyzdxi)
void add_interpolation_points_node_id(dof_id_type node_id)
static Number get_parametrized_function_node_value(const Parallel::Communicator &comm, const NodeDataMap &pf, dof_id_type node_id, unsigned int comp)
Same as get_parametrized_function_value() except for node data.
void add_side_interpolation_data(Point p, unsigned int comp, dof_id_type elem_id, unsigned int side_index, subdomain_id_type subdomain_id, boundary_id_type boundary_id, unsigned int qp, const std::vector< Point > &perturbs, const std::vector< Real > &phi_i_qp)
Add interpolation data associated with a new basis function.
DenseVector< Number > rb_eim_solve(DenseVector< Number > &EIM_rhs)
Calculate the EIM approximation for the given right-hand side vector EIM_rhs.
void get_eim_basis_function_side_values_at_qps(unsigned int basis_function_index, dof_id_type elem_id, unsigned int side_index, unsigned int var, std::vector< Number > &values) const
Same as get_eim_basis_function_values_at_qps() except for side data.
void initialize_param_fn_spatial_indices()
The Online counterpart of initialize_interpolation_points_spatial_indices().
std::vector< EIMVarGroupPlottingInfo > _eim_vars_to_project_and_write
This vector specifies which EIM variables we want to write to disk and/or project to nodes for plotti...
std::unique_ptr< RBParametrizedFunction > _parametrized_function
Store the parametrized function that will be approximated by this EIM system.
const std::vector< std::pair< Real, Real > > & get_rb_eim_error_indicators() const
Return the EIM error indicator values from the most recent call to rb_eim_solves().
RBParametrizedFunction & get_parametrized_function()
Get a reference to the parametrized function.
boundary_id_type get_interpolation_points_boundary_id(unsigned int index) const
ElemType get_interpolation_points_elem_type(unsigned int index) const
std::map< dof_id_type, std::vector< std::vector< Number > > > QpDataMap
Type of the data structure used to map from (elem id) -> [n_vars][n_qp] data.
bool get_preserve_rb_eim_solutions() const
Get _preserve_rb_eim_solutions.
std::vector< std::unique_ptr< RBTheta > > & get_eim_theta_objects()
const Point & get_elem_center_dxyzdeta(unsigned int index) const
std::vector< DenseVector< Number > > _rb_eim_solutions
The EIM solution coefficients from the most recent call to rb_eim_solves().
const Point & get_elem_center_dxyzdxi(unsigned int index) const
static Number get_parametrized_function_value(const Parallel::Communicator &comm, const QpDataMap &pf, dof_id_type elem_id, unsigned int comp, unsigned int qp)
Same as above, except that we just return the value at the qp^th quadrature point.
RBEIMEvaluation(const Parallel::Communicator &comm)
Constructor.
void get_eim_basis_function_values_at_qps(unsigned int basis_function_index, dof_id_type elem_id, unsigned int var, std::vector< Number > &values) const
Fill up values with the basis function values for basis function basis_function_index and variable va...
unsigned int get_interpolation_points_qp(unsigned int index) const
const VectorizedEvalInput & get_vec_eval_input() const
Get the VectorizedEvalInput data.
void add_interpolation_points_JxW_all_qp(const std::vector< Real > &JxW_all_qp)
void gather_bfs()
Helper function that gathers the contents of _local_eim_basis_functions to processor 0 in preparation...
const std::vector< Point > & get_interpolation_points_xyz_perturbations(unsigned int index) const
void write_out_side_basis_functions(const std::string &directory_name, bool write_binary_basis_functions)
Method that writes out element side EIM basis functions.
std::map< dof_id_type, std::vector< Number > > NodeDataMap
Type of the data structure used to map from (node id) -> [n_vars] data.
A simple functor class that provides a RBParameter-dependent function.
std::string lookup_table_param_name
If this is a lookup table, then lookup_table_param_name specifies the parameter that is used to index...
const std::unordered_map< std::string, std::set< dof_id_type > > & get_rb_property_map() const
Function that returns a reference to the rb_property_map stored in the RBParametrizedFunction.
virtual void get_spatial_indices(std::vector< std::vector< unsigned int > > &spatial_indices, const VectorizedEvalInput &v)
In some cases a parametrized function is defined based on array data that we index into based on the ...
virtual void add_interpolation_data_to_rb_property_map(const Parallel::Communicator &comm, std::unordered_map< std::string, std::set< dof_id_type > > &rb_property_map, dof_id_type elem_id)
Virtual function that can be overridden in RBParametrizedFunction subclasses to store properties in t...
const std::set< boundary_id_type > & get_parametrized_function_boundary_ids() const
For RBParametrizedFunctions defined on element sides or nodes, we get/set the boundary IDs that this ...
virtual void initialize_spatial_indices(const std::vector< std::vector< unsigned int > > &spatial_indices, const VectorizedEvalInput &v)
The Online stage counterpart of get_spatial_indices().
virtual void node_vectorized_evaluate(const std::vector< RBParameters > &mus, const VectorizedEvalInput &v, std::vector< std::vector< std::vector< Number > > > &output)
Same as vectorized_evaluate() but on element nodes.
virtual void side_vectorized_evaluate(const std::vector< RBParameters > &mus, const VectorizedEvalInput &v, std::vector< std::vector< std::vector< Number > > > &output)
Same as vectorized_evaluate() but on element sides.
virtual void vectorized_evaluate(const std::vector< RBParameters > &mus, const VectorizedEvalInput &v, std::vector< std::vector< std::vector< Number > > > &output)
Vectorized version of evaluate.
unsigned int get_n_params() const
Get the number of parameters.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
const MeshBase & get_mesh() const
Definition system.h:2401
This class implements a C++ interface to the XDR (eXternal Data Representation) format.
Definition xdr_cxx.h:68
void data(T &a, std::string_view comment="")
Inputs or outputs a single value.
Definition xdr_cxx.C:860
const boundary_id_type node_boundary_id
MeshBase & mesh
int mkdir(const char *pathname)
Create a directory.
Definition utility.C:152
The libMesh namespace provides an interface to certain functionality in the library.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
ElemType
Defines an enum for geometric element types.
int8_t boundary_id_type
Definition id_types.h:51
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
OStreamProxy out
uint8_t dof_id_type
Definition id_types.h:67
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
uint8_t processor_id_type
Definition id_types.h:104
@ INVALID_ORDER
Definition enum_order.h:88
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176
boost::multiprecision::float128 real(const boost::multiprecision::float128 in)
This struct encapsulates data that specifies how we will perform plotting for EIM variable groups.
Define a struct for the input to the "vectorized evaluate" functions below.
std::map< dof_id_type, unsigned int > elem_id_to_local_index
The following containers are indexed by element id to avoid duplicated data.
std::vector< std::vector< Point > > all_xyz_perturb
void clear()
Clear all the members.
std::unordered_map< std::string, std::set< dof_id_type > > rb_property_map
Generic map that can be used to store any list of ids (elements, nodes, elemsets, subdomains....
std::vector< unsigned int > qps
std::vector< std::vector< std::vector< Real > > > phi_i_all_qp
std::vector< dof_id_type > elem_ids
std::vector< boundary_id_type > boundary_ids
std::vector< Point > all_xyz
The members that define the inputs to the vectorized evaluate functions.
std::vector< subdomain_id_type > sbd_ids
std::vector< std::vector< Real > > JxW_all_qp
std::vector< std::vector< Real > > phi_i_qp
std::vector< unsigned int > side_indices
std::vector< dof_id_type > node_ids
static const bool value
Definition xdr_io.C:55