Loading [MathJax]/extensions/tex2jax.js
libMesh
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
rb_eim_construction.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 // C++ includes
21 #include <fstream>
22 #include <sstream>
23 
24 // LibMesh includes
25 #include "libmesh/sparse_matrix.h"
26 #include "libmesh/numeric_vector.h"
27 #include "libmesh/dense_matrix.h"
28 #include "libmesh/dense_vector.h"
29 #include "libmesh/dof_map.h"
30 #include "libmesh/libmesh_logging.h"
31 #include "libmesh/equation_systems.h"
32 #include "libmesh/parallel.h"
33 #include "libmesh/parallel_algebra.h"
34 #include "libmesh/fe.h"
35 #include "libmesh/quadrature.h"
36 #include "libmesh/utility.h"
37 #include "libmesh/fe_interface.h"
38 #include "libmesh/fe_compute_data.h"
39 #include "libmesh/getpot.h"
40 #include "libmesh/exodusII_io.h"
41 #include "libmesh/fem_context.h"
42 #include "libmesh/elem.h"
43 #include "libmesh/int_range.h"
44 
45 // rbOOmit includes
46 #include "libmesh/rb_construction_base.h"
47 #include "libmesh/rb_eim_construction.h"
48 #include "libmesh/rb_eim_evaluation.h"
49 #include "libmesh/rb_parameters.h"
50 #include "libmesh/rb_parametrized_function.h"
51 
52 // C++ include
53 #include <limits>
54 #include <memory>
55 #include <random>
56 
57 namespace libMesh
58 {
59 
60 namespace
61 {
62 // We make an anonymous namespace for local helper functions. The helper functions below
63 // are used to do some basic operators for QpDataMap and SideQpDataMap.
64 
65 // Recall that we have:
66 // typedef std::map<dof_id_type, std::vector<std::vector<Number>>> QpDataMap;
67 // typedef std::map<std::pair<dof_id_type,unsigned int>, std::vector<std::vector<Number>>> SideQpDataMap;
68 
69 // Implement u <- u + k*v
70 template <typename DataMap>
71 void add(DataMap & u, const Number k, const DataMap & v)
72 {
73  for (auto & [key, vec_vec_u] : u)
74  {
75  const std::vector<std::vector<Number>> & vec_vec_v = libmesh_map_find(v, key);
76 
77  libmesh_error_msg_if (vec_vec_u.size() != vec_vec_v.size(), "Size mismatch");
78 
79  for (auto i : index_range(vec_vec_u))
80  {
81  libmesh_error_msg_if (vec_vec_u[i].size() != vec_vec_v[i].size(), "Size mismatch");
82  for (auto j : index_range(vec_vec_u[i]))
83  {
84  vec_vec_u[i][j] += k*vec_vec_v[i][j];
85  }
86  }
87  }
88 }
89 
90 void add_node_data_map(RBEIMConstruction::NodeDataMap & u, const Number k, const RBEIMConstruction::NodeDataMap & v)
91 {
92  for (auto & [key, vec_u] : u)
93  {
94  const std::vector<Number> & vec_v = libmesh_map_find(v, key);
95 
96  libmesh_error_msg_if (vec_u.size() != vec_v.size(), "Size mismatch");
97 
98  for (auto i : index_range(vec_u))
99  {
100  vec_u[i] += k*vec_v[i];
101  }
102  }
103 }
104 
105 }
106 
108  const std::string & name_in,
109  const unsigned int number_in)
110  : RBConstructionBase(es, name_in, number_in),
111  best_fit_type_flag(PROJECTION_BEST_FIT),
112  _Nmax(0),
113  _set_Nmax_from_n_snapshots(false),
114  _Nmax_from_n_snapshots_increment(0),
115  _rel_training_tolerance(1.e-4),
116  _abs_training_tolerance(1.e-12),
117  _max_abs_value_in_training_set(0.),
118  _max_abs_value_in_training_set_index(0)
119 {
120  // The training set should be the same on all processors in the
121  // case of EIM training.
122  serial_training_set = true;
123 }
124 
126 
128 {
130 
131  _rb_eim_assembly_objects.clear();
132 
135  _local_quad_point_JxW.clear();
137 
144 
146  _local_node_locations.clear();
147  _local_node_boundary_ids.clear();
148 
150 }
151 
153 {
154  _rb_eim_eval = &rb_eim_eval_in;
155 }
156 
158 {
159  libmesh_error_msg_if(!_rb_eim_eval, "Error: RBEIMEvaluation object hasn't been initialized yet");
160  return *_rb_eim_eval;
161 }
162 
164 {
165  libmesh_error_msg_if(!_rb_eim_eval, "Error: RBEIMEvaluation object hasn't been initialized yet");
166  return *_rb_eim_eval;
167 }
168 
169 void RBEIMConstruction::set_best_fit_type_flag (const std::string & best_fit_type_string)
170 {
171  if (best_fit_type_string == "projection")
172  {
174  }
175  else if (best_fit_type_string == "eim")
176  {
178  }
179  else if (best_fit_type_string == "pod")
180  {
182  }
183  else
184  libmesh_error_msg("Error: invalid best_fit_type in input file");
185 }
186 
188 {
189  // Print out info that describes the current setup
190  libMesh::out << std::endl << "RBEIMConstruction parameters:" << std::endl;
191  libMesh::out << "system name: " << this->name() << std::endl;
192  libMesh::out << "Nmax: " << get_Nmax() << std::endl;
194  {
195  libMesh::out << "Overruling Nmax based on number of snapshots, with increment set to "
197  << std::endl;
198  }
199  libMesh::out << "Greedy relative error tolerance: " << get_rel_training_tolerance() << std::endl;
200  libMesh::out << "Greedy absolute error tolerance: " << get_abs_training_tolerance() << std::endl;
201  libMesh::out << "Number of parameters: " << get_n_params() << std::endl;
202  for (const auto & pr : get_parameters())
203  if (!is_discrete_parameter(pr.first))
204  {
205  libMesh::out << "Parameter " << pr.first
206  << ": Min = " << get_parameter_min(pr.first)
207  << ", Max = " << get_parameter_max(pr.first) << std::endl;
208  }
209 
211  libMesh::out << "n_training_samples: " << get_n_training_samples() << std::endl;
212  libMesh::out << "quiet mode? " << is_quiet() << std::endl;
213 
215  {
216  libMesh::out << "EIM best fit type: projection" << std::endl;
217  }
218  else
220  {
221  libMesh::out << "EIM best fit type: eim" << std::endl;
222  }
223  libMesh::out << std::endl;
224 }
225 
227 {
229 }
230 
231 void RBEIMConstruction::process_parameters_file (const std::string & parameters_filename)
232 {
233  // First read in data from input_filename
234  GetPot infile(parameters_filename);
235 
236  std::string best_fit_type_string = infile("best_fit_type","projection");
237  set_best_fit_type_flag(best_fit_type_string);
238 
239  const unsigned int n_training_samples = infile("n_training_samples",0);
240  const bool deterministic_training = infile("deterministic_training",false);
241  unsigned int training_parameters_random_seed_in =
242  static_cast<unsigned int>(-1);
243  training_parameters_random_seed_in = infile("training_parameters_random_seed",
244  training_parameters_random_seed_in);
245  const bool quiet_mode_in = infile("quiet_mode", quiet_mode);
246  const unsigned int Nmax_in = infile("Nmax", _Nmax);
247  const Real rel_training_tolerance_in = infile("rel_training_tolerance",
249  const Real abs_training_tolerance_in = infile("abs_training_tolerance",
251 
252  // Read in the parameters from the input file too
253  unsigned int n_continuous_parameters = infile.vector_variable_size("parameter_names");
254  RBParameters mu_min_in;
255  RBParameters mu_max_in;
256  for (unsigned int i=0; i<n_continuous_parameters; i++)
257  {
258  // Read in the parameter names
259  std::string param_name = infile("parameter_names", "NONE", i);
260 
261  {
262  Real min_val = infile(param_name, 0., 0);
263  mu_min_in.set_value(param_name, min_val);
264  }
265 
266  {
267  Real max_val = infile(param_name, 0., 1);
268  mu_max_in.set_value(param_name, max_val);
269  }
270  }
271 
272  std::map<std::string, std::vector<Real>> discrete_parameter_values_in;
273 
274  unsigned int n_discrete_parameters = infile.vector_variable_size("discrete_parameter_names");
275  for (unsigned int i=0; i<n_discrete_parameters; i++)
276  {
277  std::string param_name = infile("discrete_parameter_names", "NONE", i);
278 
279  unsigned int n_vals_for_param = infile.vector_variable_size(param_name);
280  std::vector<Real> vals_for_param(n_vals_for_param);
281  for (auto j : make_range(vals_for_param.size()))
282  vals_for_param[j] = infile(param_name, 0., j);
283 
284  discrete_parameter_values_in[param_name] = vals_for_param;
285  }
286 
287  std::map<std::string,bool> log_scaling_in;
288  // For now, just set all entries to false.
289  // TODO: Implement a decent way to specify log-scaling true/false
290  // in the input text file
291  for (const auto & pr : mu_min_in)
292  log_scaling_in[pr.first] = false;
293 
294  // Set the parameters that have been read in
295  set_rb_construction_parameters(n_training_samples,
296  deterministic_training,
297  training_parameters_random_seed_in,
298  quiet_mode_in,
299  Nmax_in,
300  rel_training_tolerance_in,
301  abs_training_tolerance_in,
302  mu_min_in,
303  mu_max_in,
304  discrete_parameter_values_in,
305  log_scaling_in);
306 }
307 
308 void RBEIMConstruction::set_rb_construction_parameters(unsigned int n_training_samples_in,
309  bool deterministic_training_in,
310  int training_parameters_random_seed_in,
311  bool quiet_mode_in,
312  unsigned int Nmax_in,
313  Real rel_training_tolerance_in,
314  Real abs_training_tolerance_in,
315  const RBParameters & mu_min_in,
316  const RBParameters & mu_max_in,
317  const std::map<std::string, std::vector<Real>> & discrete_parameter_values_in,
318  const std::map<std::string,bool> & log_scaling_in,
319  std::map<std::string, std::vector<RBParameter>> * training_sample_list)
320 {
321  // Read in training_parameters_random_seed value. This is used to
322  // seed the RNG when picking the training parameters. By default the
323  // value is -1, which means use std::time to seed the RNG.
324  set_training_random_seed(training_parameters_random_seed_in);
325 
326  // Set quiet mode
327  set_quiet_mode(quiet_mode_in);
328 
329  // Initialize RB parameters
330  set_Nmax(Nmax_in);
331 
332  set_rel_training_tolerance(rel_training_tolerance_in);
333  set_abs_training_tolerance(abs_training_tolerance_in);
334 
335  if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table)
336  {
337  const std::string & lookup_table_param_name =
339 
340  libmesh_error_msg_if(!discrete_parameter_values_in.count(lookup_table_param_name),
341  "Lookup table parameter should be discrete");
342 
343  // Make an editable copy of discrete_parameters_values_in.
344  std::map<std::string, std::vector<Real>> discrete_parameter_values_final(
345  discrete_parameter_values_in);
346 
347  std::vector<Real> & lookup_table_param_values =
348  libmesh_map_find(discrete_parameter_values_final, lookup_table_param_name);
349 
350  // Overwrite the discrete values for lookup_table_param to make sure that
351  // it is: 0, 1, 2, ..., size-1.
352  std::iota(lookup_table_param_values.begin(), lookup_table_param_values.end(), 0);
353 
354  // Also, overwrite n_training_samples_in to make sure it matches
355  // lookup_table_size so that we will get full coverage of the
356  // lookup table in our training set.
357  n_training_samples_in = lookup_table_param_values.size();
358 
359  // Initialize the parameter ranges and the parameters themselves
360  initialize_parameters(mu_min_in, mu_max_in, discrete_parameter_values_final);
361  }
362  else
363  {
364  // Initialize the parameter ranges and the parameters themselves
365  initialize_parameters(mu_min_in, mu_max_in, discrete_parameter_values_in);
366  }
367 
368  bool updated_deterministic_training = deterministic_training_in;
369  if (training_sample_list && (this->get_parameters_min().n_parameters() > 3))
370  {
371  // In this case we force deterministic_training to be false because
372  // a) deterministic training samples are not currrently supported with
373  // more than 3 parameters, and
374  // b) we will overwrite the training samples anyway in the call to
375  // load_training_set() below, so we do not want to generate an
376  // error due to deterministic training sample generation when
377  // the samples will be overwritten anyway.
378  updated_deterministic_training = false;
379  }
380 
382  this->get_parameters_max(),
383  n_training_samples_in,
384  log_scaling_in,
385  updated_deterministic_training);
386 
387  if (training_sample_list)
388  {
389  // Note that we must call initialize_training_parameters() before
390  // load_training_set() in order to initialize the parameter vectors.
391  load_training_set(*training_sample_list);
392  }
393 
394 
395  if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table)
396  {
397  // Also, now that we've initialized the training set, overwrite the training
398  // samples to ensure that we have full coverage of the lookup tbale.
399  const std::string & lookup_table_param_name =
401 
402  // Fill the lookup_table_training_samples with sequential single-entry vectors,
403  // i.e. {{0.0}, {1.0}, {2.0}, ...}
404  Real val = 0.0;
405  std::vector<RBParameter> lookup_table_training_samples(n_training_samples_in, {val});
406  for (auto & vec : lookup_table_training_samples)
407  {
408  vec[0] = val;
409  val += 1.0; // Could use val++, but better to be explicit for doubles.
410  }
411 
412  set_training_parameter_values(lookup_table_param_name, lookup_table_training_samples);
413  }
414 }
415 
417 {
420 
422  {
424  return 0.;
425  }
426  else
427  {
429  }
430 }
431 
433 {
434  LOG_SCOPE("train_eim_approximation_with_greedy()", "RBEIMConstruction");
435 
437 
438  // We need space for one extra interpolation point if we're using the
439  // EIM error indicator.
440  unsigned int max_matrix_size = rbe.use_eim_error_indicator() ? get_Nmax()+1 : get_Nmax();
441  _eim_projection_matrix.resize(max_matrix_size,max_matrix_size);
442 
443  rbe.initialize_parameters(*this);
444  rbe.resize_data_structures(max_matrix_size);
445 
446  // If we are continuing from a previous training run,
447  // we might already be at the max number of basis functions.
448  // If so, we can just return.
449  libmesh_error_msg_if(rbe.get_n_basis_functions() > 0,
450  "Error: We currently only support EIM training starting from an empty basis");
451 
452  libMesh::out << std::endl << "---- Performing Greedy EIM basis enrichment ----" << std::endl;
453 
454  // Initialize greedy_error so that we do not incorrectly set is_zero_bf=true on
455  // the first iteration.
456  Real greedy_error = -1.;
457  std::vector<RBParameters> greedy_param_list;
458 
459  // Initialize the current training index to the index that corresponds
460  // to the largest (in terms of infinity norm) function in the training set.
461  // We do this to ensure that the first EIM basis function is not zero.
462  unsigned int current_training_index = _max_abs_value_in_training_set_index;
463  set_params_from_training_set(current_training_index);
464 
465  // We use this boolean to indicate if we will run one more iteration
466  // before exiting the loop below. We use this when computing the EIM
467  // error indicator, which requires one extra EIM iteration.
468  bool exit_on_next_iteration = false;
469 
470  // We also initialize a boolean to keep track of whether we have
471  // reached "n_samples" EIM basis functions, since we need to
472  // handle the EIM error indicator in a special way in this case.
473  bool bfs_equals_n_samples = false;
474 
475  while (true)
476  {
478  {
479  libMesh::out << "Number of basis functions (" << rbe.get_n_basis_functions()
480  << ") equals number of training samples." << std::endl;
481 
482  bfs_equals_n_samples = true;
483 
484  // If exit_on_next_iteration==true then we don't exit yet, since
485  // we still need to add data for the error indicator before exiting.
486  if (!exit_on_next_iteration)
487  break;
488  }
489 
490  libMesh::out << "Greedily selected parameter vector:" << std::endl;
492  greedy_param_list.emplace_back(get_parameters());
493 
494  libMesh::out << "Enriching the EIM approximation" << std::endl;
495  libmesh_try
496  {
497  bool is_zero_bf = bfs_equals_n_samples || (greedy_error == 0.);
498 
499  // If is_zero_bf==true then we add an "extra point" because we
500  // cannot add a usual EIM interpolation point in that case since
501  // the full EIM space is already covered. This is necessary when we
502  // want to add an extra point for error indicator purposes in the
503  // is_zero_bf==true case, for example.
504  std::unique_ptr<EimPointData> eim_point_data;
505  if (is_zero_bf)
506  eim_point_data = std::make_unique<EimPointData>(get_random_point_from_training_sample());
507 
508  // If exit_on_next_iteration==true then we do not add a basis function in
509  // that case since in that case we only need to add data for the EIM error
510  // indicator.
511  enrich_eim_approximation(current_training_index,
512  /*add_basis_function*/ !exit_on_next_iteration,
513  eim_point_data.get());
514  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
515 
516  libMesh::out << std::endl << "---- Basis dimension: "
517  << rbe.get_n_basis_functions() << " ----" << std::endl;
518 
519  if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table &&
521  {
522  // If this is a lookup table and we're using "EIM best fit" then we
523  // need to update the eim_solutions after each EIM enrichment so that
524  // we can call rb_eim_eval.rb_eim_solve() from within compute_max_eim_error().
526  }
527 
528  libMesh::out << "Computing EIM error on training set" << std::endl;
529  std::tie(greedy_error, current_training_index) = compute_max_eim_error();
530  set_params_from_training_set(current_training_index);
531 
532  libMesh::out << "Maximum EIM error is " << greedy_error << std::endl << std::endl;
533  }
534 #ifdef LIBMESH_ENABLE_EXCEPTIONS
535  catch (const std::exception & e)
536  {
537  // If we hit an exception when performing the enrichment for the error indicator, then
538  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
539  if (exit_on_next_iteration)
540  {
541  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
542  break;
543  }
544  else
545  throw;
546  }
547 #endif
548 
549  if (exit_on_next_iteration)
550  {
551  libMesh::out << "Extra EIM iteration for error indicator is complete, hence exiting EIM training now" << std::endl;
552  break;
553  }
554 
555  // Convergence and/or termination tests
556  {
557  bool exit_condition_satisfied = false;
558 
559  if (rbe.get_n_basis_functions() >= this->get_Nmax())
560  {
561  libMesh::out << "Maximum number of basis functions reached: Nmax = "
562  << get_Nmax() << std::endl;
563  exit_condition_satisfied = true;
564  }
565 
566  // We consider the relative tolerance as relative to the maximum value in the training
567  // set, since we assume that this maximum value provides a relevant scaling.
568  if (!exit_condition_satisfied)
570  {
571  libMesh::out << "Relative error tolerance reached." << std::endl;
572  exit_condition_satisfied = true;
573  }
574 
575  if (!exit_condition_satisfied)
576  if (greedy_error < get_abs_training_tolerance())
577  {
578  libMesh::out << "Absolute error tolerance reached." << std::endl;
579  exit_condition_satisfied = true;
580  }
581 
582  bool has_parameters = (get_parameters().n_parameters() > 0);
583  if (!exit_condition_satisfied)
584  {
585  bool do_exit = false;
586  // In the check for repeated parameters we have to make sure this isn't a case
587  // with no parameters, since in that case we would always report repeated
588  // parameters.
589  for (auto & param : greedy_param_list)
590  if (param == get_parameters() && has_parameters)
591  {
592  libMesh::out << "Exiting greedy because the same parameters were selected twice"
593  << std::endl;
594  do_exit = true;
595  break;
596  }
597 
598  if (do_exit)
599  exit_condition_satisfied = true;
600  }
601 
602  if (exit_condition_satisfied)
603  {
604  // If we're using the EIM error indicator then we need to run
605  // one extra EIM iteration since we use the extra EIM point
606  // to obtain our error indicator. If we're not using the EIM
607  // error indicator, then we just exit now.
608  if (get_rb_eim_evaluation().use_eim_error_indicator() && has_parameters)
609  {
610  exit_on_next_iteration = true;
611  libMesh::out << "EIM error indicator is active, hence we will run one extra EIM iteration before exiting"
612  << std::endl;
613  }
614  else
615  break;
616  }
617  }
618  } // end while(true)
619 
622  {
623  // We only enter here if best_fit_type_flag != EIM_BEST_FIT because we
624  // already called this above in the EIM_BEST_FIT case.
626  }
627 
628  return greedy_error;
629 }
630 
632 {
633  LOG_SCOPE("apply_normalization_to_solution_snapshots()", "RBEIMConstruction");
634 
635  libMesh::out << "Normalizing solution snapshots" << std::endl;
636 
637  bool apply_comp_scaling = !get_rb_eim_evaluation().scale_components_in_enrichment().empty();
638  unsigned int n_snapshots = get_n_training_samples();
640 
641  for (unsigned int i=0; i<n_snapshots; i++)
642  {
643  Real norm_val = 0.;
645  {
646  norm_val = std::sqrt(std::real(side_inner_product(
649  apply_comp_scaling)));
650 
651  if (norm_val > 0.)
653  }
654  else if (rbe.get_parametrized_function().on_mesh_nodes())
655  {
656  norm_val = std::sqrt(std::real(node_inner_product(
659  apply_comp_scaling)));
660 
661  if (norm_val > 0.)
663  }
664  else
665  {
666  norm_val = std::sqrt(std::real(inner_product(
669  apply_comp_scaling)));
670 
671  if (norm_val > 0.)
673  }
674 
675  // Since we're rescaling the training samples, we should also rescale
676  // _max_abs_value_in_training_set in the same way. We obtained the
677  // _max_abs_value_in_training_set value from the sample with index
678  // _max_abs_value_in_training_set_index, so we rescale using the norm
679  // of this sample here. Note that the value we obtain may not be exactly
680  // equal to the max value we would obtain after looping over all the
681  // normalized samples, because we normalized in the L2 norm rather than
682  // in the max norm, but it should be close to this "true max" value. The
683  // main use-case of _max_abs_value_in_training_set is to scale the relative
684  // error tolerance in the Greedy training, and the L2-norm-scaled value of
685  // _max_abs_value_in_training_set that we obtain here should be sufficient
686  // for that purpose.
687  if ((i == _max_abs_value_in_training_set_index) && (norm_val > 0.))
688  _max_abs_value_in_training_set /= norm_val;
689  }
690 
691  libMesh::out << "Maximum absolute value in the training set after normalization: "
692  << _max_abs_value_in_training_set << std::endl << std::endl;
693 }
694 
696 {
697  LOG_SCOPE("train_eim_approximation_with_POD()", "RBEIMConstruction");
698 
700 
701  unsigned int n_snapshots = get_n_training_samples();
702 
703  // If _set_Nmax_from_n_snapshots=true, then we overrule Nmax.
705  {
706  int updated_Nmax = (static_cast<int>(n_snapshots) + _Nmax_from_n_snapshots_increment);
707 
708  // We only overrule _Nmax if updated_Nmax is positive, since if Nmax=0 then we'll skip
709  // training here entirely, which is typically not what we want.
710  if (updated_Nmax > 0)
711  _Nmax = static_cast<unsigned int>(updated_Nmax);
712  }
713 
714  // _eim_projection_matrix is not used in the POD case, but we resize it here in any case
715  // to be consistent with what we do in train_eim_approximation_with_greedy().
716  // We need space for one extra interpolation point if we're using the
717  // EIM error indicator.
718  unsigned int max_matrix_size = rbe.use_eim_error_indicator() ? get_Nmax()+1 : get_Nmax();
719  _eim_projection_matrix.resize(max_matrix_size,max_matrix_size);
720 
721  rbe.initialize_parameters(*this);
723 
724  libmesh_error_msg_if(rbe.get_n_basis_functions() > 0,
725  "Error: We currently only support EIM training starting from an empty basis");
726 
727  libMesh::out << std::endl << "---- Performing POD EIM basis enrichment ----" << std::endl;
728 
729  // Set up the POD "correlation matrix". This enables us to compute the POD via the
730  // "method of snapshots", in which we compute a low rank representation of the
731  // n_snapshots x n_snapshots matrix.
732  DenseMatrix<Number> correlation_matrix(n_snapshots,n_snapshots);
733 
734  std::cout << "Start computing correlation matrix" << std::endl;
735 
736  bool apply_comp_scaling = !get_rb_eim_evaluation().scale_components_in_enrichment().empty();
737  for (unsigned int i=0; i<n_snapshots; i++)
738  {
739  for (unsigned int j=0; j<=i; j++)
740  {
741  Number inner_prod = 0.;
743  {
744  inner_prod = side_inner_product(
747  apply_comp_scaling);
748  }
749  else if (rbe.get_parametrized_function().on_mesh_nodes())
750  {
751  inner_prod = node_inner_product(
754  apply_comp_scaling);
755  }
756  else
757  {
758  inner_prod = inner_product(
761  apply_comp_scaling);
762  }
763 
764 
765  correlation_matrix(i,j) = inner_prod;
766  if(i != j)
767  {
768  correlation_matrix(j,i) = libmesh_conj(inner_prod);
769  }
770  }
771 
772  // Print out every 10th row so that we can see the progress
773  if ( (i+1) % 10 == 0)
774  std::cout << "Finished row " << (i+1) << " of " << n_snapshots << std::endl;
775  }
776  std::cout << "Finished computing correlation matrix" << std::endl;
777 
778  // Compute SVD of correlation matrix.
779  // Let Y = U S V^T, then the SVD below corresponds
780  // to Y^T Y = V S U^T U S V^T = V S^2 V^T.
781  // The POD basis we use is then given by U, which
782  // we can compute via U = Y V S^{-1}, which is what
783  // we compute below.
784  //
785  // Note that the formulation remains the same in the
786  // case that we use a weighted inner product (as
787  // in the case that we used apply_comp_scaling=true
788  // when computing the correlation matrix), see (1.28)
789  // from the lecture notes from Volkwein on POD for more
790  // details.
791  DenseVector<Real> sigma( n_snapshots );
792  DenseMatrix<Number> U( n_snapshots, n_snapshots );
793  DenseMatrix<Number> VT( n_snapshots, n_snapshots );
794  correlation_matrix.svd(sigma, U, VT );
795 
796  // We use this boolean to indicate if we will run one more iteration
797  // before exiting the loop below. We use this when computing the EIM
798  // error indicator, which requires one extra EIM iteration.
799  bool exit_on_next_iteration = false;
800 
801  // Add dominant vectors from the POD as basis functions.
802  unsigned int j = 0;
803  Real rel_err = 0.;
804 
805  // We also initialize a boolean to keep track of whether we have
806  // reached "n_snapshots" EIM basis functions, since we need to
807  // handle the EIM error indicator in a special way in this case.
808  bool j_equals_n_snapshots = false;
809  while (true)
810  {
811  bool exit_condition_satisfied = false;
812 
813  if ((j == 0) && (sigma(0) == 0.))
814  {
815  libMesh::out << "Terminating EIM POD with empty basis because first singular value is zero" << std::endl;
816  exit_condition_satisfied = true;
817  rel_err = 0.;
818  }
819  else if (j >= n_snapshots)
820  {
821  libMesh::out << "Number of basis functions equals number of training samples." << std::endl;
822  exit_condition_satisfied = true;
823  j_equals_n_snapshots = true;
824 
825  // In this case we set the rel. error to be zero since we've filled up the
826  // entire space. We cannot use the formula below for rel_err since
827  // sigma(n_snapshots) is not defined.
828  rel_err = 0.;
829  }
830  else
831  {
832  // The "energy" error in the POD approximation is determined by the first omitted
833  // singular value, i.e. sigma(j). We normalize by sigma(0), which gives the total
834  // "energy", in order to obtain a relative error.
835  rel_err = std::sqrt(sigma(j)) / std::sqrt(sigma(0));
836  }
837 
838  if (exit_on_next_iteration)
839  {
840  libMesh::out << "Extra EIM iteration for error indicator is complete, POD error norm for extra iteration: " << rel_err << std::endl;
841  break;
842  }
843 
844  libMesh::out << "Number of basis functions: " << j
845  << ", POD error norm: " << rel_err << std::endl;
846 
847  if (!exit_condition_satisfied)
848  if (j >= get_Nmax())
849  {
850  libMesh::out << "Maximum number of basis functions (" << j << ") reached." << std::endl;
851  exit_condition_satisfied = true;
852  }
853 
854  if (!exit_condition_satisfied)
855  if (rel_err < get_rel_training_tolerance())
856  {
857  libMesh::out << "Training tolerance reached." << std::endl;
858  exit_condition_satisfied = true;
859  }
860 
861  if (exit_condition_satisfied)
862  {
863  // If we're using the EIM error indicator then we need to run
864  // one extra EIM iteration since we use the extra EIM point
865  // to obtain our error indicator. If we're not using the EIM
866  // error indicator, then we just exit now.
867  bool has_parameters = (get_parameters().n_parameters() > 0);
868  if (get_rb_eim_evaluation().use_eim_error_indicator() && has_parameters)
869  {
870  exit_on_next_iteration = true;
871  libMesh::out << "EIM error indicator is active, hence we will run one extra EIM iteration before exiting"
872  << std::endl;
873  }
874  else
875  break;
876  }
877 
878  bool is_zero_bf = j_equals_n_snapshots || (rel_err == 0.);
880  {
881  // Make a "zero clone" by copying to get the same data layout, and then scaling by zero
883 
884  if (!is_zero_bf)
885  {
887 
888  for ( unsigned int i=0; i<n_snapshots; ++i )
890 
891  Real norm_v = std::sqrt(sigma(j));
892  scale_parametrized_function(v, 1./norm_v);
893  }
894 
895  libmesh_try
896  {
897  // If is_zero_bf==true then we add an "extra point" because we cannot
898  // add a usual EIM interpolation point in that case since the full EIM
899  // space is already covered. This is necessary when we want to add an
900  // extra point for error indicator purposes in the is_zero_bf==true
901  // case, for example.
902  std::unique_ptr<EimPointData> eim_point_data;
903  if (is_zero_bf)
904  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
905 
906  // If exit_on_next_iteration==true then we do not add a basis function in
907  // that case since in that case we only need to add data for the EIM error
908  // indicator.
909  bool is_linearly_dependent = enrich_eim_approximation_on_sides(v,
910  /*add_basis_function*/ !exit_on_next_iteration,
911  eim_point_data.get());
912 
913  if (is_linearly_dependent && !is_zero_bf)
914  {
915  // In this case we detected that v is actually linearly dependent and that is_zero_bf
916  // was previously not correct --- it should have been true. We typically
917  // catch this earlier (e.g. by checking rel_err) but in some cases we do not catch
918  // this until we call the enrichment method. In this situation we update is_zero_bf
919  // to true and call the enrichment again.
920  is_zero_bf = true;
921  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
922 
924  /*add_basis_function*/ !exit_on_next_iteration,
925  eim_point_data.get());
926  }
927 
928  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
929  }
930 #ifdef LIBMESH_ENABLE_EXCEPTIONS
931  catch (const std::exception & e)
932  {
933  // If we hit an exception when performing the enrichment for the error indicator, then
934  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
935  if (exit_on_next_iteration)
936  {
937  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
938  break;
939  }
940  else
941  throw;
942  }
943 #endif
944  }
945  else if (rbe.get_parametrized_function().on_mesh_nodes())
946  {
947  // Make a "zero clone" by copying to get the same data layout, and then scaling by zero
949 
950  if (!is_zero_bf)
951  {
953 
954  for ( unsigned int i=0; i<n_snapshots; ++i )
955  add_node_data_map(v, U.el(i, j), _local_node_parametrized_functions_for_training[i] );
956 
957  Real norm_v = std::sqrt(sigma(j));
958  scale_node_parametrized_function(v, 1./norm_v);
959  }
960 
961  libmesh_try
962  {
963  // If is_zero_bf==true then we add an "extra point" because we cannot
964  // add a usual EIM interpolation point in that case since the full EIM
965  // space is already covered. This is necessary when we want to add an
966  // extra point for error indicator purposes in the is_zero_bf==true
967  // case, for example.
968  std::unique_ptr<EimPointData> eim_point_data;
969  if (is_zero_bf)
970  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
971 
972  // If exit_on_next_iteration==true then we do not add a basis function in
973  // that case since in that case we only need to add data for the EIM error
974  // indicator.
975  bool is_linearly_dependent = enrich_eim_approximation_on_nodes(v,
976  /*add_basis_function*/ !exit_on_next_iteration,
977  eim_point_data.get());
978 
979  if (is_linearly_dependent && !is_zero_bf)
980  {
981  // In this case we detected that v is actually linearly dependent and that is_zero_bf
982  // was previously not correct --- it should have been true. We typically
983  // catch this earlier (e.g. by checking rel_err) but in some cases we do not catch
984  // this until we call the enrichment method. In this situation we update is_zero_bf
985  // to true and call the enrichment again.
986  is_zero_bf = true;
987  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
988 
990  /*add_basis_function*/ !exit_on_next_iteration,
991  eim_point_data.get());
992  }
993 
994  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
995  }
996 #ifdef LIBMESH_ENABLE_EXCEPTIONS
997  catch (const std::exception & e)
998  {
999  // If we hit an exception when performing the enrichment for the error indicator, then
1000  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
1001  if (exit_on_next_iteration)
1002  {
1003  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
1004  break;
1005  }
1006  else
1007  throw;
1008  }
1009 #endif
1010  }
1011  else
1012  {
1013  // Make a "zero clone" by copying to get the same data layout, and then scaling by zero
1015 
1016  if (!is_zero_bf)
1017  {
1019 
1020  for ( unsigned int i=0; i<n_snapshots; ++i )
1021  add(v, U.el(i, j), _local_parametrized_functions_for_training[i] );
1022 
1023  Real norm_v = std::sqrt(sigma(j));
1024  scale_parametrized_function(v, 1./norm_v);
1025  }
1026 
1027  libmesh_try
1028  {
1029  // If is_zero_bf==true then we add an "extra point" because we cannot
1030  // add a usual EIM interpolation point in that case since the full EIM
1031  // space is already covered. This is necessary when we want to add an
1032  // extra point for error indicator purposes in the is_zero_bf==true
1033  // case, for example.
1034  std::unique_ptr<EimPointData> eim_point_data;
1035  if (is_zero_bf)
1036  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
1037 
1038  // If exit_on_next_iteration==true then we do not add a basis function in
1039  // that case since in that case we only need to add data for the EIM error
1040  // indicator.
1041  bool is_linearly_dependent = enrich_eim_approximation_on_interiors(v,
1042  /*add_basis_function*/ !exit_on_next_iteration,
1043  eim_point_data.get());
1044 
1045  if (is_linearly_dependent && !is_zero_bf)
1046  {
1047  // In this case we detected that v is actually linearly dependent and that is_zero_bf
1048  // was previously not correct --- it should have been true. We typically
1049  // catch this earlier (e.g. by checking rel_err) but in some cases we do not catch
1050  // this until we call the enrichment method. In this situation we update is_zero_bf
1051  // to true and call the enrichment again.
1052  is_zero_bf = true;
1053  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
1054 
1056  /*add_basis_function*/ !exit_on_next_iteration,
1057  eim_point_data.get());
1058  }
1059 
1060  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
1061  }
1062 #ifdef LIBMESH_ENABLE_EXCEPTIONS
1063  catch (const std::exception & e)
1064  {
1065  // If we hit an exception when performing the enrichment for the error indicator, then
1066  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
1067  if (exit_on_next_iteration)
1068  {
1069  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
1070  break;
1071  }
1072  else
1073  throw;
1074  }
1075 #endif
1076  }
1077 
1078  if (is_zero_bf)
1079  {
1080  // In this case we exit here instead of increment j and continuing because
1081  // if we've encountered a zero EIM basis function then we must not have
1082  // any more valid data to add.
1083  std::cout << "Zero basis function encountered, hence exiting." << std::endl;
1084  break;
1085  }
1086 
1087  j++;
1088  }
1089  libMesh::out << std::endl;
1090 
1091  return rel_err;
1092 }
1093 
1095 {
1096  _rb_eim_assembly_objects.clear();
1097  for (auto i : make_range(get_rb_eim_evaluation().get_n_basis_functions()))
1099 }
1100 
1101 std::vector<std::unique_ptr<ElemAssembly>> & RBEIMConstruction::get_eim_assembly_objects()
1102 {
1103  return _rb_eim_assembly_objects;
1104 }
1105 
1107 {
1108  // Pre-request FE data for all element dimensions present in the
1109  // mesh. Note: we currently pre-request FE data for all variables
1110  // in the current system but in some cases that may be overkill, for
1111  // example if only variable 0 is used.
1112  const System & sys = c.get_system();
1113  const MeshBase & mesh = sys.get_mesh();
1114 
1115  for (unsigned int dim=1; dim<=3; ++dim)
1116  if (mesh.elem_dimensions().count(dim))
1117  for (auto var : make_range(sys.n_vars()))
1118  {
1119  auto fe = c.get_element_fe(var, dim);
1120  fe->get_JxW();
1121  fe->get_xyz();
1122  fe->get_phi();
1123 
1124  auto side_fe = c.get_side_fe(var, dim);
1125  side_fe->get_JxW();
1126  side_fe->get_xyz();
1127  side_fe->get_phi();
1128  }
1129 }
1130 
1132 {
1133  _rel_training_tolerance = new_training_tolerance;
1134 }
1135 
1137 {
1138  return _rel_training_tolerance;
1139 }
1140 
1142 {
1143  _abs_training_tolerance = new_training_tolerance;
1144 }
1145 
1147 {
1148  return _abs_training_tolerance;
1149 }
1150 
1151 unsigned int RBEIMConstruction::get_Nmax() const
1152 {
1153  return _Nmax;
1154 }
1155 
1156 void RBEIMConstruction::set_Nmax(unsigned int Nmax)
1157 {
1158  _Nmax = Nmax;
1159 }
1160 
1162 {
1165 }
1166 
1168 {
1171 }
1172 
1174 {
1176 }
1177 
1179 {
1180  LOG_SCOPE("store_eim_solutions_for_training_set()", "RBEIMConstruction");
1181 
1182  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
1183 
1184  std::vector<DenseVector<Number>> & eim_solutions = get_rb_eim_evaluation().get_eim_solutions_for_training_set();
1185  eim_solutions.clear();
1186  eim_solutions.resize(get_n_training_samples());
1187 
1188  unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
1189 
1190  for (auto i : make_range(get_n_training_samples()))
1191  {
1192  if (eim_eval.get_parametrized_function().on_mesh_sides())
1193  {
1194  const auto & local_side_pf = _local_side_parametrized_functions_for_training[i];
1195 
1196  if (RB_size > 0)
1197  {
1198  // Get the right-hand side vector for the EIM approximation
1199  // by sampling the parametrized function (stored in solution)
1200  // at the interpolation points.
1201  DenseVector<Number> EIM_rhs(RB_size);
1202  for (unsigned int j=0; j<RB_size; j++)
1203  {
1204  EIM_rhs(j) =
1206  local_side_pf,
1209  eim_eval.get_interpolation_points_comp(j),
1210  eim_eval.get_interpolation_points_qp(j));
1211  }
1212  eim_solutions[i] = eim_eval.rb_eim_solve(EIM_rhs);
1213  }
1214  }
1215  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
1216  {
1217  const auto & local_node_pf = _local_node_parametrized_functions_for_training[i];
1218 
1219  if (RB_size > 0)
1220  {
1221  // Get the right-hand side vector for the EIM approximation
1222  // by sampling the parametrized function (stored in solution)
1223  // at the interpolation points.
1224  DenseVector<Number> EIM_rhs(RB_size);
1225  for (unsigned int j=0; j<RB_size; j++)
1226  {
1227  EIM_rhs(j) =
1229  local_node_pf,
1231  eim_eval.get_interpolation_points_comp(j));
1232  }
1233  eim_solutions[i] = eim_eval.rb_eim_solve(EIM_rhs);
1234  }
1235  }
1236  else
1237  {
1238  const auto & local_pf = _local_parametrized_functions_for_training[i];
1239 
1240  if (RB_size > 0)
1241  {
1242  // Get the right-hand side vector for the EIM approximation
1243  // by sampling the parametrized function (stored in solution)
1244  // at the interpolation points.
1245  DenseVector<Number> EIM_rhs(RB_size);
1246  for (unsigned int j=0; j<RB_size; j++)
1247  {
1248  EIM_rhs(j) =
1250  local_pf,
1252  eim_eval.get_interpolation_points_comp(j),
1253  eim_eval.get_interpolation_points_qp(j));
1254  }
1255  eim_solutions[i] = eim_eval.rb_eim_solve(EIM_rhs);
1256  }
1257  }
1258  }
1259 }
1260 
1262 {
1263  libmesh_error_msg_if(training_index >= _local_parametrized_functions_for_training.size(),
1264  "Invalid index: " << training_index);
1265  return _local_parametrized_functions_for_training[training_index];
1266 }
1267 
1269 {
1270  libmesh_error_msg_if(training_index >= _local_side_parametrized_functions_for_training.size(),
1271  "Invalid index: " << training_index);
1272  return _local_side_parametrized_functions_for_training[training_index];
1273 }
1274 
1276 {
1277  libmesh_error_msg_if(training_index >= _local_node_parametrized_functions_for_training.size(),
1278  "Invalid index: " << training_index);
1279  return _local_node_parametrized_functions_for_training[training_index];
1280 }
1281 
1282 const std::unordered_map<dof_id_type, std::vector<Real> > & RBEIMConstruction::get_local_quad_point_JxW()
1283 {
1284  return _local_quad_point_JxW;
1285 }
1286 
1287 const std::map<std::pair<dof_id_type,unsigned int>, std::vector<Real> > & RBEIMConstruction::get_local_side_quad_point_JxW()
1288 {
1290 }
1291 
1293 {
1294  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1296  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1298  else
1300 }
1301 
1303 {
1305 
1306  // We need space for one extra interpolation point if we're using the
1307  // EIM error indicator.
1308  unsigned int max_matrix_size = rbe.use_eim_error_indicator() ? get_Nmax()+1 : get_Nmax();
1309  _eim_projection_matrix.resize(max_matrix_size,max_matrix_size);
1310 }
1311 
1312 std::pair<Real,unsigned int> RBEIMConstruction::compute_max_eim_error()
1313 {
1314  LOG_SCOPE("compute_max_eim_error()", "RBEIMConstruction");
1315 
1316  if (get_n_params() == 0)
1317  {
1318  // Just return 0 if we have no parameters.
1319  return std::make_pair(0.,0);
1320  }
1321 
1322  // keep track of the maximum error
1323  unsigned int max_err_index = 0;
1324  Real max_err = 0.;
1325 
1326  libmesh_error_msg_if(get_n_training_samples() != get_local_n_training_samples(),
1327  "Error: Training samples should be the same on all procs");
1328 
1329  const unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
1330 
1332  {
1333  for (auto training_index : make_range(get_n_training_samples()))
1334  {
1335  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1336  {
1337  // Make a copy of the pre-computed solution for the specified training sample
1338  // since we will modify it below to compute the best fit error.
1339  SideQpDataMap solution_copy = _local_side_parametrized_functions_for_training[training_index];
1340 
1341  // Perform an L2 projection in order to find the best approximation to
1342  // the parametrized function from the current EIM space.
1343  DenseVector<Number> best_fit_rhs(RB_size);
1344  for (unsigned int i=0; i<RB_size; i++)
1345  {
1346  best_fit_rhs(i) = side_inner_product(solution_copy,
1347  get_rb_eim_evaluation().get_side_basis_function(i),
1348  /*apply_comp_scaling*/ false);
1349  }
1350 
1351  // Now compute the best fit by an LU solve
1352  DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
1353  _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
1354 
1355  DenseVector<Number> best_fit_coeffs;
1356  RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
1357 
1358  get_rb_eim_evaluation().side_decrement_vector(solution_copy, best_fit_coeffs);
1359  Real best_fit_error = get_max_abs_value(solution_copy);
1360 
1361  if (best_fit_error > max_err)
1362  {
1363  max_err_index = training_index;
1364  max_err = best_fit_error;
1365  }
1366  }
1367  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1368  {
1369  // Make a copy of the pre-computed solution for the specified training sample
1370  // since we will modify it below to compute the best fit error.
1371  NodeDataMap solution_copy = _local_node_parametrized_functions_for_training[training_index];
1372 
1373  // Perform an L2 projection in order to find the best approximation to
1374  // the parametrized function from the current EIM space.
1375  DenseVector<Number> best_fit_rhs(RB_size);
1376  for (unsigned int i=0; i<RB_size; i++)
1377  {
1378  best_fit_rhs(i) = node_inner_product(solution_copy,
1379  get_rb_eim_evaluation().get_node_basis_function(i),
1380  /*apply_comp_scaling*/ false);
1381  }
1382 
1383  // Now compute the best fit by an LU solve
1384  DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
1385  _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
1386 
1387  DenseVector<Number> best_fit_coeffs;
1388  RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
1389 
1390  get_rb_eim_evaluation().node_decrement_vector(solution_copy, best_fit_coeffs);
1391  Real best_fit_error = get_node_max_abs_value(solution_copy);
1392 
1393  if (best_fit_error > max_err)
1394  {
1395  max_err_index = training_index;
1396  max_err = best_fit_error;
1397  }
1398  }
1399  else
1400  {
1401  // Make a copy of the pre-computed solution for the specified training sample
1402  // since we will modify it below to compute the best fit error.
1403  QpDataMap solution_copy = _local_parametrized_functions_for_training[training_index];
1404 
1405  // Perform an L2 projection in order to find the best approximation to
1406  // the parametrized function from the current EIM space.
1407  DenseVector<Number> best_fit_rhs(RB_size);
1408  for (unsigned int i=0; i<RB_size; i++)
1409  {
1410  best_fit_rhs(i) = inner_product(solution_copy,
1411  get_rb_eim_evaluation().get_basis_function(i),
1412  /*apply_comp_scaling*/ false);
1413  }
1414 
1415  // Now compute the best fit by an LU solve
1416  DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
1417  _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
1418 
1419  DenseVector<Number> best_fit_coeffs;
1420  RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
1421 
1422  get_rb_eim_evaluation().decrement_vector(solution_copy, best_fit_coeffs);
1423  Real best_fit_error = get_max_abs_value(solution_copy);
1424 
1425  if (best_fit_error > max_err)
1426  {
1427  max_err_index = training_index;
1428  max_err = best_fit_error;
1429  }
1430  }
1431  }
1432  }
1433  else if(best_fit_type_flag == EIM_BEST_FIT)
1434  {
1435  // Perform EIM solve in order to find the approximation to solution
1436  // (rb_eim_solve provides the EIM basis function coefficients used below)
1437 
1438  std::vector<RBParameters> training_parameters_copy(get_n_training_samples());
1439  for (auto training_index : make_range(get_n_training_samples()))
1440  {
1441  training_parameters_copy[training_index] = get_params_from_training_set(training_index);
1442  }
1443 
1444  get_rb_eim_evaluation().rb_eim_solves(training_parameters_copy, RB_size);
1445  const std::vector<DenseVector<Number>> & rb_eim_solutions = get_rb_eim_evaluation().get_rb_eim_solutions();
1446 
1447  for (auto training_index : make_range(get_n_training_samples()))
1448  {
1449  const DenseVector<Number> & best_fit_coeffs = rb_eim_solutions[training_index];
1450 
1451  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1452  {
1453  SideQpDataMap solution_copy = _local_side_parametrized_functions_for_training[training_index];
1454  get_rb_eim_evaluation().side_decrement_vector(solution_copy, best_fit_coeffs);
1455  Real best_fit_error = get_max_abs_value(solution_copy);
1456 
1457  if (best_fit_error > max_err)
1458  {
1459  max_err_index = training_index;
1460  max_err = best_fit_error;
1461  }
1462  }
1463  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1464  {
1465  NodeDataMap solution_copy = _local_node_parametrized_functions_for_training[training_index];
1466  get_rb_eim_evaluation().node_decrement_vector(solution_copy, best_fit_coeffs);
1467  Real best_fit_error = get_node_max_abs_value(solution_copy);
1468 
1469  if (best_fit_error > max_err)
1470  {
1471  max_err_index = training_index;
1472  max_err = best_fit_error;
1473  }
1474  }
1475  else
1476  {
1477  QpDataMap solution_copy = _local_parametrized_functions_for_training[training_index];
1478  get_rb_eim_evaluation().decrement_vector(solution_copy, best_fit_coeffs);
1479  Real best_fit_error = get_max_abs_value(solution_copy);
1480 
1481  if (best_fit_error > max_err)
1482  {
1483  max_err_index = training_index;
1484  max_err = best_fit_error;
1485  }
1486  }
1487  }
1488  }
1489  else
1490  {
1491  libmesh_error_msg("EIM best fit type not recognized");
1492  }
1493 
1494  return std::make_pair(max_err,max_err_index);
1495 }
1496 
1498 {
1499  LOG_SCOPE("initialize_parametrized_functions_in_training_set()", "RBEIMConstruction");
1500 
1501  libmesh_error_msg_if(!serial_training_set,
1502  "Error: We must have serial_training_set==true in "
1503  "RBEIMConstruction::initialize_parametrized_functions_in_training_set");
1504 
1505  libMesh::out << "Initializing parametrized functions in training set..." << std::endl;
1506 
1507  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
1508 
1511 
1512  // Store the locations of all quadrature points
1514 
1515  // Keep track of the largest value in our parametrized functions
1516  // in the training set. We can use this value for normalization
1517  // purposes, for example.
1519 
1520  unsigned int n_comps = eim_eval.get_parametrized_function().get_n_components();
1521 
1522  // Keep track of the maximum value per component. This will allow
1523  // us to scale the components to all have a similar magnitude,
1524  // which is helpful during the error assessment for the basis
1525  // enrichment to ensure that components with smaller magnitude
1526  // are not ignored.
1527  std::vector<Real> max_abs_value_per_component_in_training_set(n_comps);
1528 
1529  if (eim_eval.get_parametrized_function().on_mesh_sides())
1530  {
1532  for (auto i : make_range(get_n_training_samples()))
1533  {
1534  libMesh::out << "Initializing parametrized function for training sample "
1535  << (i+1) << " of " << get_n_training_samples() << std::endl;
1536 
1538 
1545  *this);
1546 
1547  for (const auto & [elem_side_pair, xyz_vector] : _local_side_quad_point_locations)
1548  {
1549  std::vector<std::vector<Number>> comps_and_qps(n_comps);
1550  for (unsigned int comp=0; comp<n_comps; comp++)
1551  {
1552  comps_and_qps[comp].resize(xyz_vector.size());
1553  for (unsigned int qp : index_range(xyz_vector))
1554  {
1555  Number value =
1557  elem_side_pair.first,
1558  elem_side_pair.second,
1559  qp);
1560  comps_and_qps[comp][qp] = value;
1561 
1562  Real abs_value = std::abs(value);
1563  if (abs_value > _max_abs_value_in_training_set)
1564  {
1565  _max_abs_value_in_training_set = abs_value;
1567  }
1568 
1569  if (abs_value > max_abs_value_per_component_in_training_set[comp])
1570  max_abs_value_per_component_in_training_set[comp] = abs_value;
1571  }
1572  }
1573 
1574  _local_side_parametrized_functions_for_training[i][elem_side_pair] = comps_and_qps;
1575  }
1576  }
1577 
1578  libMesh::out << "Parametrized functions in training set initialized" << std::endl;
1579 
1580  unsigned int max_id = 0;
1583  libMesh::out << "Maximum absolute value in the training set: "
1584  << _max_abs_value_in_training_set << std::endl << std::endl;
1585 
1586  // Calculate the maximum value for each component in the training set
1587  // across all components
1588  comm().max(max_abs_value_per_component_in_training_set);
1589 
1590  // We store the maximum value across all components divided by the maximum value for this component
1591  // so that when we scale using these factors all components should have a magnitude on the same
1592  // order as the maximum component.
1593  _component_scaling_in_training_set.resize(n_comps);
1594  for (unsigned int i : make_range(n_comps))
1595  {
1596  if ((eim_eval.scale_components_in_enrichment().count(i) == 0) ||
1597  max_abs_value_per_component_in_training_set[i] == 0.)
1599  else
1600  _component_scaling_in_training_set[i] = _max_abs_value_in_training_set / max_abs_value_per_component_in_training_set[i];
1601  }
1602  }
1603  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
1604  {
1606  for (auto i : make_range(get_n_training_samples()))
1607  {
1608  libMesh::out << "Initializing parametrized function for training sample "
1609  << (i+1) << " of " << get_n_training_samples() << std::endl;
1610 
1612 
1616  *this);
1617 
1618  for (const auto & pr : _local_node_locations)
1619  {
1620  const auto & node_id = pr.first;
1621 
1622  std::vector<Number> comps(n_comps);
1623  for (unsigned int comp=0; comp<n_comps; comp++)
1624  {
1625  Number value =
1627  node_id);
1628  comps[comp] = value;
1629 
1630  Real abs_value = std::abs(value);
1631  if (abs_value > _max_abs_value_in_training_set)
1632  {
1633  _max_abs_value_in_training_set = abs_value;
1635  }
1636 
1637  if (abs_value > max_abs_value_per_component_in_training_set[comp])
1638  max_abs_value_per_component_in_training_set[comp] = abs_value;
1639  }
1640 
1642  }
1643  }
1644 
1645  libMesh::out << "Parametrized functions in training set initialized" << std::endl;
1646 
1647  unsigned int max_id = 0;
1650  libMesh::out << "Maximum absolute value in the training set: "
1651  << _max_abs_value_in_training_set << std::endl << std::endl;
1652 
1653  // Calculate the maximum value for each component in the training set
1654  // across all components
1655  comm().max(max_abs_value_per_component_in_training_set);
1656 
1657  // We store the maximum value across all components divided by the maximum value for this component
1658  // so that when we scale using these factors all components should have a magnitude on the same
1659  // order as the maximum component.
1660  _component_scaling_in_training_set.resize(n_comps);
1661  for (unsigned int i : make_range(n_comps))
1662  {
1663  if ((eim_eval.scale_components_in_enrichment().count(i) == 0) ||
1664  max_abs_value_per_component_in_training_set[i] == 0.)
1666  else
1667  _component_scaling_in_training_set[i] = _max_abs_value_in_training_set / max_abs_value_per_component_in_training_set[i];
1668  }
1669  }
1670  else
1671  {
1673  for (auto i : make_range(get_n_training_samples()))
1674  {
1675  libMesh::out << "Initializing parametrized function for training sample "
1676  << (i+1) << " of " << get_n_training_samples() << std::endl;
1677 
1679 
1684  *this);
1685 
1686  for (const auto & [elem_id, xyz_vector] : _local_quad_point_locations)
1687  {
1688  std::vector<std::vector<Number>> comps_and_qps(n_comps);
1689  for (unsigned int comp=0; comp<n_comps; comp++)
1690  {
1691  comps_and_qps[comp].resize(xyz_vector.size());
1692  for (unsigned int qp : index_range(xyz_vector))
1693  {
1694  Number value =
1695  eim_eval.get_parametrized_function().lookup_preevaluated_value_on_mesh(comp, elem_id, qp);
1696  comps_and_qps[comp][qp] = value;
1697 
1698  Real abs_value = std::abs(value);
1699  if (abs_value > _max_abs_value_in_training_set)
1700  {
1701  _max_abs_value_in_training_set = abs_value;
1703  }
1704 
1705  if (abs_value > max_abs_value_per_component_in_training_set[comp])
1706  max_abs_value_per_component_in_training_set[comp] = abs_value;
1707  }
1708  }
1709 
1710  _local_parametrized_functions_for_training[i][elem_id] = comps_and_qps;
1711  }
1712  }
1713 
1714  libMesh::out << "Parametrized functions in training set initialized" << std::endl;
1715 
1716  unsigned int max_id = 0;
1719  libMesh::out << "Maximum absolute value in the training set: "
1720  << _max_abs_value_in_training_set << std::endl << std::endl;
1721 
1722  // Calculate the maximum value for each component in the training set
1723  // across all components
1724  comm().max(max_abs_value_per_component_in_training_set);
1725 
1726  // We store the maximum value across all components divided by the maximum value for this component
1727  // so that when we scale using these factors all components should have a magnitude on the same
1728  // order as the maximum component.
1729  _component_scaling_in_training_set.resize(n_comps);
1730  for (unsigned int i : make_range(n_comps))
1731  {
1732  if ((eim_eval.scale_components_in_enrichment().count(i) == 0) ||
1733  max_abs_value_per_component_in_training_set[i] == 0.)
1735  else
1736  _component_scaling_in_training_set[i] = _max_abs_value_in_training_set / max_abs_value_per_component_in_training_set[i];
1737  }
1738  }
1739  // This function does nothing if rb_property_map from RBParametrizedFunction
1740  // is empty which would result in an empty rb_property_map in VectorizedEvalInput
1741  // stored in RBEIMEvaluation.
1742  eim_eval.initialize_rb_property_map();
1743 }
1744 
1746 {
1747  LOG_SCOPE("initialize_qp_data()", "RBEIMConstruction");
1748 
1749  if (!get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
1750  {
1751  libMesh::out << "Initializing quadrature point locations" << std::endl;
1752  }
1753  else
1754  {
1755  libMesh::out << "Initializing quadrature point and perturbation locations" << std::endl;
1756  }
1757 
1758  // Compute truth representation via L2 projection
1759  const MeshBase & mesh = this->get_mesh();
1760 
1761  FEMContext context(*this);
1762  init_context(context);
1763 
1764  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1765  {
1766  const std::set<boundary_id_type> & parametrized_function_boundary_ids =
1768  libmesh_error_msg_if (parametrized_function_boundary_ids.empty(),
1769  "Need to have non-empty boundary IDs to initialize side data");
1770 
1776 
1778 
1779  // BoundaryInfo and related data structures
1780  const auto & binfo = mesh.get_boundary_info();
1781  std::vector<boundary_id_type> side_boundary_ids;
1782 
1783  for (const auto & elem : mesh.active_local_element_ptr_range())
1784  {
1785  dof_id_type elem_id = elem->id();
1786 
1787  context.pre_fe_reinit(*this, elem);
1788 
1789  // elem_fe is used for shellface data
1790  auto elem_fe = context.get_element_fe(/*var=*/0, elem->dim());
1791  const std::vector<Real> & JxW = elem_fe->get_JxW();
1792  const std::vector<Point> & xyz = elem_fe->get_xyz();
1793 
1794  // side_fe is used for element side data
1795  auto side_fe = context.get_side_fe(/*var=*/0, elem->dim());
1796  const std::vector<Real> & JxW_side = side_fe->get_JxW();
1797  const std::vector< Point > & xyz_side = side_fe->get_xyz();
1798 
1799  for (context.side = 0;
1800  context.side != context.get_elem().n_sides();
1801  ++context.side)
1802  {
1803  // skip non-boundary elements
1804  if(!context.get_elem().neighbor_ptr(context.side))
1805  {
1806  binfo.boundary_ids(elem, context.side, side_boundary_ids);
1807 
1808  bool has_side_boundary_id = false;
1809  boundary_id_type matching_boundary_id = BoundaryInfo::invalid_id;
1810  for (boundary_id_type side_boundary_id : side_boundary_ids)
1811  if(parametrized_function_boundary_ids.count(side_boundary_id))
1812  {
1813  has_side_boundary_id = true;
1814  matching_boundary_id = side_boundary_id;
1815  break;
1816  }
1817 
1818  if(has_side_boundary_id)
1819  {
1820  context.get_side_fe(/*var=*/0, elem->dim())->reinit(elem, context.side);
1821 
1822  auto elem_side_pair = std::make_pair(elem_id, context.side);
1823 
1824  _local_side_quad_point_locations[elem_side_pair] = xyz_side;
1825  _local_side_quad_point_JxW[elem_side_pair] = JxW_side;
1826  _local_side_quad_point_subdomain_ids[elem_side_pair] = elem->subdomain_id();
1827  _local_side_quad_point_boundary_ids[elem_side_pair] = matching_boundary_id;
1828 
1829  // This is a standard side (not a shellface) so set side type to 0
1830  _local_side_quad_point_side_types[elem_side_pair] = 0;
1831 
1832  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
1833  {
1835 
1836  std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
1837 
1838  for (const Point & xyz_qp : xyz_side)
1839  {
1840  std::vector<Point> xyz_perturb_vec;
1841  if (elem->dim() == 3)
1842  {
1843  // In this case we have a 3D element, and hence the side is 2D.
1844  //
1845  // We use the following approach to perturb xyz:
1846  // 1) inverse map xyz to the reference element
1847  // 2) perturb on the reference element in the (xi,eta) "directions"
1848  // 3) map the perturbed points back to the physical element
1849  // This approach is necessary to ensure that the perturbed points
1850  // are still in the element's side.
1851 
1852  std::unique_ptr<const Elem> elem_side;
1853  elem->build_side_ptr(elem_side, context.side);
1854 
1855  Point xi_eta =
1856  FEMap::inverse_map(elem_side->dim(),
1857  elem_side.get(),
1858  xyz_qp,
1859  /*Newton iteration tolerance*/ TOLERANCE,
1860  /*secure*/ true);
1861 
1862  // Inverse map should map back to a 2D reference domain
1863  libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
1864 
1865  Point xi_eta_perturb = xi_eta;
1866 
1867  xi_eta_perturb(0) += fd_delta;
1868  Point xyz_perturb_0 =
1869  FEMap::map(elem_side->dim(),
1870  elem_side.get(),
1871  xi_eta_perturb);
1872  xi_eta_perturb(0) -= fd_delta;
1873 
1874  xi_eta_perturb(1) += fd_delta;
1875  Point xyz_perturb_1 =
1876  FEMap::map(elem_side->dim(),
1877  elem_side.get(),
1878  xi_eta_perturb);
1879  xi_eta_perturb(1) -= fd_delta;
1880 
1881  // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
1882  // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
1883  // required in order to compute finite differences correctly.
1884  Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
1885  Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
1886 
1887  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
1888  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
1889  }
1890  else
1891  {
1892  // We current do nothing for sides of dim=2 or dim=1 elements
1893  // since we have no need for this capability so far.
1894  // Support for these cases could be added if it is needed.
1895  }
1896 
1897  xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
1898  }
1899 
1900  _local_side_quad_point_locations_perturbations[elem_side_pair] = xyz_perturb_vec_at_qps;
1901  }
1902  }
1903  }
1904  }
1905 
1906  // In the case of 2D elements, we also check the shellfaces
1907  if (elem->dim() == 2)
1908  for (unsigned int shellface_index=0; shellface_index<2; shellface_index++)
1909  {
1910  binfo.shellface_boundary_ids(elem, shellface_index, side_boundary_ids);
1911 
1912  bool has_side_boundary_id = false;
1913  boundary_id_type matching_boundary_id = BoundaryInfo::invalid_id;
1914  for (boundary_id_type side_boundary_id : side_boundary_ids)
1915  if(parametrized_function_boundary_ids.count(side_boundary_id))
1916  {
1917  has_side_boundary_id = true;
1918  matching_boundary_id = side_boundary_id;
1919  break;
1920  }
1921 
1922  if(has_side_boundary_id)
1923  {
1924  context.elem_fe_reinit();
1925 
1926  // We use shellface_index as the side_index since shellface boundary conditions
1927  // are stored separately from side boundary conditions in BoundaryInfo.
1928  auto elem_side_pair = std::make_pair(elem_id, shellface_index);
1929 
1930  _local_side_quad_point_locations[elem_side_pair] = xyz;
1931  _local_side_quad_point_JxW[elem_side_pair] = JxW;
1932  _local_side_quad_point_subdomain_ids[elem_side_pair] = elem->subdomain_id();
1933  _local_side_quad_point_boundary_ids[elem_side_pair] = matching_boundary_id;
1934 
1935  // This is a shellface (not a standard side) so set side type to 1
1936  _local_side_quad_point_side_types[elem_side_pair] = 1;
1937 
1938  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
1939  {
1941 
1942  std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
1943 
1944  for (const Point & xyz_qp : xyz)
1945  {
1946  std::vector<Point> xyz_perturb_vec;
1947  // Here we follow the same approach as above for getting xyz_perturb_vec,
1948  // except that we are using the element itself instead of its side.
1949  {
1950  Point xi_eta =
1951  FEMap::inverse_map(elem->dim(),
1952  elem,
1953  xyz_qp,
1954  /*Newton iteration tolerance*/ TOLERANCE,
1955  /*secure*/ true);
1956 
1957  // Inverse map should map back to a 2D reference domain
1958  libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
1959 
1960  Point xi_eta_perturb = xi_eta;
1961 
1962  xi_eta_perturb(0) += fd_delta;
1963  Point xyz_perturb_0 =
1964  FEMap::map(elem->dim(),
1965  elem,
1966  xi_eta_perturb);
1967  xi_eta_perturb(0) -= fd_delta;
1968 
1969  xi_eta_perturb(1) += fd_delta;
1970  Point xyz_perturb_1 =
1971  FEMap::map(elem->dim(),
1972  elem,
1973  xi_eta_perturb);
1974  xi_eta_perturb(1) -= fd_delta;
1975 
1976  // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
1977  // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
1978  // required in order to compute finite differences correctly.
1979  Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
1980  Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
1981 
1982  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
1983  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
1984  }
1985 
1986  xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
1987  }
1988 
1989  _local_side_quad_point_locations_perturbations[elem_side_pair] = xyz_perturb_vec_at_qps;
1990  }
1991  }
1992  }
1993  }
1994  }
1995  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1996  {
1997  const std::set<boundary_id_type> & parametrized_function_boundary_ids =
1999  libmesh_error_msg_if (parametrized_function_boundary_ids.empty(),
2000  "Need to have non-empty boundary IDs to initialize node data");
2001 
2002  _local_node_locations.clear();
2003  _local_node_boundary_ids.clear();
2004 
2005  const auto & binfo = mesh.get_boundary_info();
2006 
2007  // Make a set with all the nodes that have nodesets. Use
2008  // a set so that we don't have any duplicate entries. We
2009  // deal with duplicate entries below by getting all boundary
2010  // IDs on each node.
2011  std::set<dof_id_type> nodes_with_nodesets;
2012  for (const auto & t : binfo.build_node_list())
2013  nodes_with_nodesets.insert(std::get<0>(t));
2014 
2015  // To be filled in by BoundaryInfo calls in loop below
2016  std::vector<boundary_id_type> node_boundary_ids;
2017 
2018  for (dof_id_type node_id : nodes_with_nodesets)
2019  {
2020  const Node * node = mesh.node_ptr(node_id);
2021 
2022  if (node->processor_id() != mesh.comm().rank())
2023  continue;
2024 
2025  binfo.boundary_ids(node, node_boundary_ids);
2026 
2027  bool has_node_boundary_id = false;
2028  boundary_id_type matching_boundary_id = BoundaryInfo::invalid_id;
2029  for (boundary_id_type node_boundary_id : node_boundary_ids)
2030  if(parametrized_function_boundary_ids.count(node_boundary_id))
2031  {
2032  has_node_boundary_id = true;
2033  matching_boundary_id = node_boundary_id;
2034  break;
2035  }
2036 
2037  if(has_node_boundary_id)
2038  {
2039  _local_node_locations[node_id] = *node;
2040  _local_node_boundary_ids[node_id] = matching_boundary_id;
2041  }
2042  }
2043  }
2044  else
2045  {
2048  _local_quad_point_JxW.clear();
2049 
2051 
2052  for (const auto & elem : mesh.active_local_element_ptr_range())
2053  {
2054  auto elem_fe = context.get_element_fe(/*var=*/0, elem->dim());
2055  const std::vector<Real> & JxW = elem_fe->get_JxW();
2056  const std::vector<Point> & xyz = elem_fe->get_xyz();
2057 
2058  dof_id_type elem_id = elem->id();
2059 
2060  context.pre_fe_reinit(*this, elem);
2061  context.elem_fe_reinit();
2062 
2063  _local_quad_point_locations[elem_id] = xyz;
2064  _local_quad_point_JxW[elem_id] = JxW;
2065  _local_quad_point_subdomain_ids[elem_id] = elem->subdomain_id();
2066 
2067  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
2068  {
2070 
2071  std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
2072 
2073  for (const Point & xyz_qp : xyz)
2074  {
2075  std::vector<Point> xyz_perturb_vec;
2076  if (elem->dim() == 3)
2077  {
2078  Point xyz_perturb = xyz_qp;
2079 
2080  xyz_perturb(0) += fd_delta;
2081  xyz_perturb_vec.emplace_back(xyz_perturb);
2082  xyz_perturb(0) -= fd_delta;
2083 
2084  xyz_perturb(1) += fd_delta;
2085  xyz_perturb_vec.emplace_back(xyz_perturb);
2086  xyz_perturb(1) -= fd_delta;
2087 
2088  xyz_perturb(2) += fd_delta;
2089  xyz_perturb_vec.emplace_back(xyz_perturb);
2090  xyz_perturb(2) -= fd_delta;
2091  }
2092  else if (elem->dim() == 2)
2093  {
2094  // In this case we assume that we have a 2D element
2095  // embedded in 3D space. In this case we have to use
2096  // the following approach to perturb xyz:
2097  // 1) inverse map xyz to the reference element
2098  // 2) perturb on the reference element in the (xi,eta) "directions"
2099  // 3) map the perturbed points back to the physical element
2100  // This approach is necessary to ensure that the perturbed points
2101  // are still in the element.
2102 
2103  Point xi_eta =
2104  FEMap::inverse_map(elem->dim(),
2105  elem,
2106  xyz_qp,
2107  /*Newton iteration tolerance*/ TOLERANCE,
2108  /*secure*/ true);
2109 
2110  // Inverse map should map back to a 2D reference domain
2111  libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
2112 
2113  Point xi_eta_perturb = xi_eta;
2114 
2115  xi_eta_perturb(0) += fd_delta;
2116  Point xyz_perturb_0 =
2117  FEMap::map(elem->dim(),
2118  elem,
2119  xi_eta_perturb);
2120  xi_eta_perturb(0) -= fd_delta;
2121 
2122  xi_eta_perturb(1) += fd_delta;
2123  Point xyz_perturb_1 =
2124  FEMap::map(elem->dim(),
2125  elem,
2126  xi_eta_perturb);
2127  xi_eta_perturb(1) -= fd_delta;
2128 
2129  // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
2130  // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
2131  // required in order to compute finite differences correctly.
2132  Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
2133  Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
2134 
2135  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
2136  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
2137  }
2138  else
2139  {
2140  // We current do nothing in the dim=1 case since
2141  // we have no need for this capability so far.
2142  // Support for this case could be added if it is
2143  // needed.
2144  }
2145 
2146  xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
2147  }
2148 
2149  _local_quad_point_locations_perturbations[elem_id] = xyz_perturb_vec_at_qps;
2150  }
2151  }
2152  }
2153 }
2154 
2155 Number
2156 RBEIMConstruction::inner_product(const QpDataMap & v, const QpDataMap & w, bool apply_comp_scaling)
2157 {
2158  LOG_SCOPE("inner_product()", "RBEIMConstruction");
2159 
2160  Number val = 0.;
2161 
2162  for (const auto & [elem_id, v_comp_and_qp] : v)
2163  {
2164  const auto & w_comp_and_qp = libmesh_map_find(w, elem_id);
2165  const auto & JxW = libmesh_map_find(_local_quad_point_JxW, elem_id);
2166 
2167  for (const auto & comp : index_range(v_comp_and_qp))
2168  {
2169  const std::vector<Number> & v_qp = v_comp_and_qp[comp];
2170  const std::vector<Number> & w_qp = w_comp_and_qp[comp];
2171 
2172  Real comp_scaling = 1.;
2173  if (apply_comp_scaling)
2174  {
2175  // We square the component scaling here because it occurs twice in
2176  // the inner product calculation below.
2177  comp_scaling = std::pow(_component_scaling_in_training_set[comp], 2.);
2178  }
2179 
2180  for (unsigned int qp : index_range(JxW))
2181  val += JxW[qp] * comp_scaling * v_qp[qp] * libmesh_conj(w_qp[qp]);
2182  }
2183  }
2184 
2185  comm().sum(val);
2186  return val;
2187 }
2188 
2189 Number
2190 RBEIMConstruction::side_inner_product(const SideQpDataMap & v, const SideQpDataMap & w, bool apply_comp_scaling)
2191 {
2192  LOG_SCOPE("side_inner_product()", "RBEIMConstruction");
2193 
2194  Number val = 0.;
2195 
2196  for (const auto & [elem_and_side, v_comp_and_qp] : v)
2197  {
2198  const auto & w_comp_and_qp = libmesh_map_find(w, elem_and_side);
2199  const auto & JxW = libmesh_map_find(_local_side_quad_point_JxW, elem_and_side);
2200 
2201  for (const auto & comp : index_range(v_comp_and_qp))
2202  {
2203  const std::vector<Number> & v_qp = v_comp_and_qp[comp];
2204  const std::vector<Number> & w_qp = w_comp_and_qp[comp];
2205 
2206  Real comp_scaling = 1.;
2207  if (apply_comp_scaling)
2208  {
2209  // We square the component scaling here because it occurs twice in
2210  // the inner product calculation below.
2211  comp_scaling = std::pow(_component_scaling_in_training_set[comp], 2.);
2212  }
2213 
2214  for (unsigned int qp : index_range(JxW))
2215  val += JxW[qp] * comp_scaling * v_qp[qp] * libmesh_conj(w_qp[qp]);
2216  }
2217  }
2218 
2219  comm().sum(val);
2220  return val;
2221 }
2222 
2223 Number
2224 RBEIMConstruction::node_inner_product(const NodeDataMap & v, const NodeDataMap & w, bool apply_comp_scaling)
2225 {
2226  LOG_SCOPE("node_inner_product()", "RBEIMConstruction");
2227 
2228  Number val = 0.;
2229 
2230  for (const auto & [node_id, v_comps] : v)
2231  {
2232  const auto & w_comps = libmesh_map_find(w, node_id);
2233 
2234  for (const auto & comp : index_range(v_comps))
2235  {
2236  // There is no quadrature rule on nodes, so we just multiply the values directly.
2237  // Hence we effectively work with the Euclidean inner product in this case.
2238 
2239  Real comp_scaling = 1.;
2240  if (apply_comp_scaling)
2241  {
2242  // We square the component scaling here because it occurs twice in
2243  // the inner product calculation below.
2244  comp_scaling = std::pow(_component_scaling_in_training_set[comp], 2.);
2245  }
2246 
2247  val += comp_scaling * v_comps[comp] * libmesh_conj(w_comps[comp]);
2248  }
2249  }
2250 
2251  comm().sum(val);
2252  return val;
2253 }
2254 
2256 {
2257  Real max_value = 0.;
2258 
2259  for (const auto & pr : v)
2260  {
2261  const auto & values = pr.second;
2262  for (const auto & comp : index_range(values))
2263  {
2264  const auto & value = values[comp];
2265 
2266  Real comp_scaling = 1.;
2267  if (get_rb_eim_evaluation().scale_components_in_enrichment().count(comp))
2268  {
2269  // Make sure that _component_scaling_in_training_set is initialized
2270  libmesh_error_msg_if(comp >= _component_scaling_in_training_set.size(),
2271  "Invalid vector index");
2272  comp_scaling = _component_scaling_in_training_set[comp];
2273  }
2274 
2275  max_value = std::max(max_value, std::abs(value * comp_scaling));
2276  }
2277  }
2278 
2279  comm().max(max_value);
2280  return max_value;
2281 }
2282 
2283 void RBEIMConstruction::enrich_eim_approximation(unsigned int training_index,
2284  bool add_basis_function,
2285  EimPointData * eim_point_data)
2286 {
2287  LOG_SCOPE("enrich_eim_approximation()", "RBEIMConstruction");
2288 
2289  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2290 
2291  set_params_from_training_set(training_index);
2292 
2293  if (eim_eval.get_parametrized_function().on_mesh_sides())
2295  add_basis_function,
2296  eim_point_data);
2297  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
2299  add_basis_function,
2300  eim_point_data);
2301  else
2302  {
2304  add_basis_function,
2305  eim_point_data);
2306  }
2307 }
2308 
2310  bool add_basis_function,
2311  EimPointData * eim_point_data)
2312 {
2313  // Make a copy of the input parametrized function, since we will modify this below
2314  // to give us a new basis function.
2315  SideQpDataMap local_pf = side_pf;
2316 
2317  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2318 
2319  // If we have at least one basis function, then we need to use
2320  // rb_eim_solve() to find the EIM interpolation error. Otherwise,
2321  // just use solution as is.
2322  if (!eim_point_data && (eim_eval.get_n_basis_functions() > 0))
2323  {
2324  // Get the right-hand side vector for the EIM approximation
2325  // by sampling the parametrized function (stored in solution)
2326  // at the interpolation points.
2327  unsigned int RB_size = eim_eval.get_n_basis_functions();
2328  DenseVector<Number> EIM_rhs(RB_size);
2329  for (unsigned int i=0; i<RB_size; i++)
2330  {
2331  EIM_rhs(i) =
2333  local_pf,
2336  eim_eval.get_interpolation_points_comp(i),
2337  eim_eval.get_interpolation_points_qp(i));
2338  }
2339 
2340  eim_eval.set_parameters( get_parameters() );
2341  DenseVector<Number> rb_eim_solution = eim_eval.rb_eim_solve(EIM_rhs);
2342 
2343  // Load the "EIM residual" into solution by subtracting
2344  // the EIM approximation
2345  eim_eval.side_decrement_vector(local_pf, rb_eim_solution);
2346  }
2347 
2348  // Find the quadrature point at which local_pf (which now stores
2349  // the "EIM residual") has maximum absolute value
2350  Number optimal_value = 0.;
2351  Point optimal_point;
2352  unsigned int optimal_comp = 0;
2353  dof_id_type optimal_elem_id = DofObject::invalid_id;
2354  unsigned int optimal_side_index = 0;
2355  subdomain_id_type optimal_subdomain_id = 0;
2356  boundary_id_type optimal_boundary_id = 0;
2357  unsigned int optimal_qp = 0;
2358  std::vector<Point> optimal_point_perturbs;
2359  std::vector<Real> optimal_point_phi_i_qp;
2360 
2361  // Initialize largest_abs_value to be negative so that it definitely gets updated.
2362  Real largest_abs_value = -1.;
2363 
2364  // In order to compute phi_i_qp, we initialize a FEMContext
2365  FEMContext con(*this);
2366  init_context(con);
2367 
2368  for (const auto & [elem_and_side, comp_and_qp] : local_pf)
2369  {
2370  dof_id_type elem_id = elem_and_side.first;
2371  unsigned int side_index = elem_and_side.second;
2372 
2373  const Elem & elem_ref = get_mesh().elem_ref(elem_id);
2374  con.pre_fe_reinit(*this, &elem_ref);
2375 
2376  unsigned int side_type = libmesh_map_find(_local_side_quad_point_side_types, elem_and_side);
2377 
2378  std::vector<std::vector<Real>> phi;
2379  // side_type == 0 --> standard side
2380  // side_type == 1 --> shellface
2381  if (side_type == 0)
2382  {
2383  // TODO: We only want the "dofs on side" entries
2384  // from phi_side. Could do this by initing an FE object
2385  // on the side itself, rather than using get_side_fe().
2386  auto side_fe = con.get_side_fe(/*var=*/ 0);
2387  side_fe->reinit(&elem_ref, side_index);
2388 
2389  phi = side_fe->get_phi();
2390  }
2391  else if (side_type == 1)
2392  {
2393  con.elem_fe_reinit();
2394 
2395  auto elem_fe = con.get_element_fe(/*var=*/0, elem_ref.dim());
2396  phi = elem_fe->get_phi();
2397  }
2398  else
2399  libmesh_error_msg ("Unrecognized side_type: " << side_type);
2400 
2401  for (const auto & comp : index_range(comp_and_qp))
2402  {
2403  const std::vector<Number> & qp_values = comp_and_qp[comp];
2404 
2405  for (auto qp : index_range(qp_values))
2406  {
2407  Number value = qp_values[qp];
2408  Real abs_value = std::abs(value);
2409 
2410  bool update_optimal_point = false;
2411  if (!eim_point_data)
2412  update_optimal_point = (abs_value > largest_abs_value);
2413  else
2414  update_optimal_point = (elem_id == eim_point_data->elem_id) &&
2415  (side_index == eim_point_data->side_index) &&
2416  (comp == eim_point_data->comp_index) &&
2417  (qp == eim_point_data->qp_index);
2418 
2419  if (update_optimal_point)
2420  {
2421  largest_abs_value = abs_value;
2422  optimal_value = value;
2423  optimal_comp = comp;
2424  optimal_elem_id = elem_id;
2425  optimal_side_index = side_index;
2426  optimal_qp = qp;
2427 
2428  optimal_point_phi_i_qp.resize(phi.size());
2429  for (auto i : index_range(phi))
2430  optimal_point_phi_i_qp[i] = phi[i][qp];
2431 
2432  const auto & point_list =
2433  libmesh_map_find(_local_side_quad_point_locations, elem_and_side);
2434 
2435  libmesh_error_msg_if(qp >= point_list.size(), "Error: Invalid qp");
2436 
2437  optimal_point = point_list[qp];
2438 
2439  optimal_subdomain_id = libmesh_map_find(_local_side_quad_point_subdomain_ids, elem_and_side);
2440  optimal_boundary_id = libmesh_map_find(_local_side_quad_point_boundary_ids, elem_and_side);
2441 
2442  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
2443  {
2444  const auto & perturb_list =
2445  libmesh_map_find(_local_side_quad_point_locations_perturbations, elem_and_side);
2446 
2447  libmesh_error_msg_if(qp >= perturb_list.size(), "Error: Invalid qp");
2448 
2449  optimal_point_perturbs = perturb_list[qp];
2450  }
2451  }
2452  }
2453  }
2454  }
2455 
2456  // Find out which processor has the largest of the abs values
2457  // and broadcast from that processor.
2458  unsigned int proc_ID_index;
2459  this->comm().maxloc(largest_abs_value, proc_ID_index);
2460 
2461  this->comm().broadcast(optimal_value, proc_ID_index);
2462  this->comm().broadcast(optimal_point, proc_ID_index);
2463  this->comm().broadcast(optimal_comp, proc_ID_index);
2464  this->comm().broadcast(optimal_elem_id, proc_ID_index);
2465  this->comm().broadcast(optimal_side_index, proc_ID_index);
2466  this->comm().broadcast(optimal_subdomain_id, proc_ID_index);
2467  this->comm().broadcast(optimal_boundary_id, proc_ID_index);
2468  this->comm().broadcast(optimal_qp, proc_ID_index);
2469  this->comm().broadcast(optimal_point_perturbs, proc_ID_index);
2470  this->comm().broadcast(optimal_point_phi_i_qp, proc_ID_index);
2471 
2472  libmesh_error_msg_if(optimal_elem_id == DofObject::invalid_id, "Error: Invalid element ID");
2473 
2474  if (add_basis_function)
2475  {
2476  if (optimal_value == 0.)
2477  {
2478  libMesh::out << "Encountered linearly dependent data in EIM enrichment, hence skip adding new basis function" << std::endl;
2479  return true;
2480  }
2481 
2482  // Scale local_pf so that its largest value is 1.0
2483  scale_parametrized_function(local_pf, 1./optimal_value);
2484 
2485  // Add local_pf as the new basis function and store data
2486  // associated with the interpolation point.
2487  eim_eval.add_side_basis_function(local_pf);
2488  }
2489 
2490  eim_eval.add_side_interpolation_data(optimal_point,
2491  optimal_comp,
2492  optimal_elem_id,
2493  optimal_side_index,
2494  optimal_subdomain_id,
2495  optimal_boundary_id,
2496  optimal_qp,
2497  optimal_point_perturbs,
2498  optimal_point_phi_i_qp);
2499 
2500  // In this case we did not encounter a linearly dependent basis function, so return false
2501  return false;
2502 }
2503 
2505  bool add_basis_function,
2506  EimPointData * eim_point_data)
2507 {
2508  // Make a copy of the input parametrized function, since we will modify this below
2509  // to give us a new basis function.
2510  NodeDataMap local_pf = node_pf;
2511 
2512  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2513 
2514  // If we have at least one basis function, then we need to use
2515  // rb_eim_solve() to find the EIM interpolation error. Otherwise,
2516  // just use solution as is.
2517  if (!eim_point_data && (eim_eval.get_n_basis_functions() > 0))
2518  {
2519  // Get the right-hand side vector for the EIM approximation
2520  // by sampling the parametrized function (stored in solution)
2521  // at the interpolation points.
2522  unsigned int RB_size = eim_eval.get_n_basis_functions();
2523  DenseVector<Number> EIM_rhs(RB_size);
2524  for (unsigned int i=0; i<RB_size; i++)
2525  {
2526  EIM_rhs(i) =
2528  local_pf,
2530  eim_eval.get_interpolation_points_comp(i));
2531  }
2532 
2533  eim_eval.set_parameters( get_parameters() );
2534  DenseVector<Number> rb_eim_solution = eim_eval.rb_eim_solve(EIM_rhs);
2535 
2536  // Load the "EIM residual" into solution by subtracting
2537  // the EIM approximation
2538  eim_eval.node_decrement_vector(local_pf, rb_eim_solution);
2539  }
2540 
2541  // Find the quadrature point at which local_pf (which now stores
2542  // the "EIM residual") has maximum absolute value
2543  Number optimal_value = 0.;
2544  Point optimal_point;
2545  unsigned int optimal_comp = 0;
2546  dof_id_type optimal_node_id = DofObject::invalid_id;
2547  boundary_id_type optimal_boundary_id = 0;
2548 
2549  // Initialize largest_abs_value to be negative so that it definitely gets updated.
2550  Real largest_abs_value = -1.;
2551 
2552  for (const auto & [node_id, values] : local_pf)
2553  {
2554  for (unsigned int comp : index_range(values))
2555  {
2556  Number value = values[comp];
2557  Real abs_value = std::abs(value);
2558 
2559  bool update_optimal_point = false;
2560  if (!eim_point_data)
2561  update_optimal_point = (abs_value > largest_abs_value);
2562  else
2563  update_optimal_point = (node_id == eim_point_data->node_id) &&
2564  (comp == eim_point_data->comp_index);
2565 
2566  if (update_optimal_point)
2567  {
2568  largest_abs_value = abs_value;
2569  optimal_value = value;
2570  optimal_comp = comp;
2571  optimal_node_id = node_id;
2572 
2573  optimal_point = libmesh_map_find(_local_node_locations, node_id);
2574 
2575  optimal_boundary_id = libmesh_map_find(_local_node_boundary_ids, node_id);
2576  }
2577  }
2578  }
2579 
2580  // Find out which processor has the largest of the abs values
2581  // and broadcast from that processor.
2582  unsigned int proc_ID_index;
2583  this->comm().maxloc(largest_abs_value, proc_ID_index);
2584 
2585  this->comm().broadcast(optimal_value, proc_ID_index);
2586  this->comm().broadcast(optimal_point, proc_ID_index);
2587  this->comm().broadcast(optimal_comp, proc_ID_index);
2588  this->comm().broadcast(optimal_node_id, proc_ID_index);
2589  this->comm().broadcast(optimal_boundary_id, proc_ID_index);
2590 
2591  libmesh_error_msg_if(optimal_node_id == DofObject::invalid_id, "Error: Invalid node ID");
2592 
2593  if (add_basis_function)
2594  {
2595  if (optimal_value == 0.)
2596  {
2597  libMesh::out << "Encountered linearly dependent data in EIM enrichment, hence skip adding new basis function" << std::endl;
2598  return true;
2599  }
2600 
2601  // Scale local_pf so that its largest value is 1.0
2602  scale_node_parametrized_function(local_pf, 1./optimal_value);
2603 
2604  // Add local_pf as the new basis function and store data
2605  // associated with the interpolation point.
2606  eim_eval.add_node_basis_function(local_pf);
2607  }
2608 
2609  eim_eval.add_node_interpolation_data(optimal_point,
2610  optimal_comp,
2611  optimal_node_id,
2612  optimal_boundary_id);
2613 
2614  // In this case we did not encounter a linearly dependent basis function, so return false
2615  return false;
2616 }
2617 
2619  bool add_basis_function,
2620  EimPointData * eim_point_data)
2621 {
2622  // Make a copy of the input parametrized function, since we will modify this below
2623  // to give us a new basis function.
2624  QpDataMap local_pf = interior_pf;
2625 
2626  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2627 
2628  // If we have at least one basis function, then we need to use
2629  // rb_eim_solve() to find the EIM interpolation error. Otherwise,
2630  // just use solution as is.
2631  if (!eim_point_data && (eim_eval.get_n_basis_functions() > 0))
2632  {
2633  // Get the right-hand side vector for the EIM approximation
2634  // by sampling the parametrized function (stored in solution)
2635  // at the interpolation points.
2636  unsigned int RB_size = eim_eval.get_n_basis_functions();
2637  DenseVector<Number> EIM_rhs(RB_size);
2638  for (unsigned int i=0; i<RB_size; i++)
2639  {
2640  EIM_rhs(i) =
2642  local_pf,
2644  eim_eval.get_interpolation_points_comp(i),
2645  eim_eval.get_interpolation_points_qp(i));
2646  }
2647 
2648  eim_eval.set_parameters( get_parameters() );
2649  DenseVector<Number> rb_eim_solution = eim_eval.rb_eim_solve(EIM_rhs);
2650 
2651  // Load the "EIM residual" into solution by subtracting
2652  // the EIM approximation
2653  eim_eval.decrement_vector(local_pf, rb_eim_solution);
2654  }
2655 
2656  // Find the quadrature point at which local_pf (which now stores
2657  // the "EIM residual") has maximum absolute value
2658  Number optimal_value = 0.;
2659  Point optimal_point;
2660  unsigned int optimal_comp = 0;
2661  dof_id_type optimal_elem_id = DofObject::invalid_id;
2662  subdomain_id_type optimal_subdomain_id = 0;
2663  unsigned int optimal_qp = 0;
2664  std::vector<Point> optimal_point_perturbs;
2665  std::vector<Real> optimal_point_phi_i_qp;
2666  ElemType optimal_elem_type = INVALID_ELEM;
2667  std::vector<Real> optimal_JxW_all_qp;
2668  std::vector<std::vector<Real>> optimal_phi_i_all_qp;
2669  Order optimal_qrule_order = INVALID_ORDER;
2670  Point optimal_dxyzdxi_elem_center;
2671  Point optimal_dxyzdeta_elem_center;
2672 
2673  // Initialize largest_abs_value to be negative so that it definitely gets updated.
2674  Real largest_abs_value = -1.;
2675 
2676  // In order to compute phi_i_qp, we initialize a FEMContext
2677  FEMContext con(*this);
2678  for (auto dim : con.elem_dimensions())
2679  {
2680  auto fe = con.get_element_fe(/*var=*/0, dim);
2681  fe->get_phi();
2682  fe->get_JxW();
2683  fe->get_dxyzdxi();
2684  fe->get_dxyzdeta();
2685  }
2686 
2687  for (const auto & [elem_id, comp_and_qp] : local_pf)
2688  {
2689  // Also initialize phi in order to compute phi_i_qp
2690  const Elem & elem_ref = get_mesh().elem_ref(elem_id);
2691  con.pre_fe_reinit(*this, &elem_ref);
2692 
2693  auto elem_fe = con.get_element_fe(/*var=*/0, elem_ref.dim());
2694  const std::vector<std::vector<Real>> & phi = elem_fe->get_phi();
2695  const auto & JxW = elem_fe->get_JxW();
2696  const auto & dxyzdxi = elem_fe->get_dxyzdxi();
2697  const auto & dxyzdeta = elem_fe->get_dxyzdeta();
2698 
2699  elem_fe->reinit(&elem_ref);
2700 
2701  for (const auto & comp : index_range(comp_and_qp))
2702  {
2703  const std::vector<Number> & qp_values = comp_and_qp[comp];
2704 
2705  for (auto qp : index_range(qp_values))
2706  {
2707  Number value = qp_values[qp];
2708  Real abs_value = std::abs(value);
2709 
2710  if (get_rb_eim_evaluation().scale_components_in_enrichment().count(comp))
2711  abs_value *= _component_scaling_in_training_set[comp];
2712 
2713  bool update_optimal_point = false;
2714  if (!eim_point_data)
2715  update_optimal_point = (abs_value > largest_abs_value);
2716  else
2717  update_optimal_point = (elem_id == eim_point_data->elem_id) &&
2718  (comp == eim_point_data->comp_index) &&
2719  (qp == eim_point_data->qp_index);
2720 
2721  if (update_optimal_point)
2722  {
2723  largest_abs_value = abs_value;
2724  optimal_value = value;
2725  optimal_comp = comp;
2726  optimal_elem_id = elem_id;
2727  optimal_qp = qp;
2728  optimal_elem_type = elem_ref.type();
2729 
2730  optimal_point_phi_i_qp.resize(phi.size());
2731  for (auto i : index_range(phi))
2732  optimal_point_phi_i_qp[i] = phi[i][qp];
2733 
2734  const auto & point_list =
2735  libmesh_map_find(_local_quad_point_locations, elem_id);
2736 
2737  libmesh_error_msg_if(qp >= point_list.size(), "Error: Invalid qp");
2738 
2739  optimal_point = point_list[qp];
2740 
2741  optimal_subdomain_id = libmesh_map_find(_local_quad_point_subdomain_ids, elem_id);
2742 
2743  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
2744  {
2745  const auto & perturb_list =
2746  libmesh_map_find(_local_quad_point_locations_perturbations, elem_id);
2747 
2748  libmesh_error_msg_if(qp >= perturb_list.size(), "Error: Invalid qp");
2749 
2750  optimal_point_perturbs = perturb_list[qp];
2751  }
2752 
2754  {
2755  optimal_JxW_all_qp = JxW;
2756  optimal_phi_i_all_qp = phi;
2757  }
2758 
2760  {
2761  optimal_qrule_order = con.get_element_qrule().get_order();
2762  // Get data derivatives at vertex average
2763  std::vector<Point> nodes = { elem_ref.reference_elem()->vertex_average() };
2764  elem_fe->reinit (&elem_ref, &nodes);
2765 
2766  Point dxyzdxi_pt, dxyzdeta_pt;
2767  if (con.get_elem_dim()>0)
2768  dxyzdxi_pt = dxyzdxi[0];
2769  if (con.get_elem_dim()>1)
2770  dxyzdeta_pt = dxyzdeta[0];
2771 
2772  optimal_dxyzdxi_elem_center = dxyzdxi_pt;
2773  optimal_dxyzdeta_elem_center = dxyzdeta_pt;
2774 
2775  elem_fe->reinit(&elem_ref);
2776  }
2777  }
2778  }
2779  }
2780  }
2781 
2782  // Find out which processor has the largest of the abs values
2783  // and broadcast from that processor.
2784  unsigned int proc_ID_index;
2785  this->comm().maxloc(largest_abs_value, proc_ID_index);
2786 
2787  this->comm().broadcast(optimal_value, proc_ID_index);
2788  this->comm().broadcast(optimal_point, proc_ID_index);
2789  this->comm().broadcast(optimal_comp, proc_ID_index);
2790  this->comm().broadcast(optimal_elem_id, proc_ID_index);
2791  this->comm().broadcast(optimal_subdomain_id, proc_ID_index);
2792  this->comm().broadcast(optimal_qp, proc_ID_index);
2793  this->comm().broadcast(optimal_point_perturbs, proc_ID_index);
2794  this->comm().broadcast(optimal_point_phi_i_qp, proc_ID_index);
2795  this->comm().broadcast(optimal_JxW_all_qp, proc_ID_index);
2796  this->comm().broadcast(optimal_phi_i_all_qp, proc_ID_index);
2797  this->comm().broadcast(optimal_dxyzdxi_elem_center, proc_ID_index);
2798  this->comm().broadcast(optimal_dxyzdeta_elem_center, proc_ID_index);
2799 
2800  // Cast optimal_elem_type to an int in order to broadcast it
2801  {
2802  int optimal_elem_type_int = static_cast<int>(optimal_elem_type);
2803  this->comm().broadcast(optimal_elem_type_int, proc_ID_index);
2804  optimal_elem_type = static_cast<ElemType>(optimal_elem_type_int);
2805  }
2806 
2807  // Cast optimal_qrule_order to an int in order to broadcast it
2808  {
2809  int optimal_qrule_order_int = static_cast<int>(optimal_qrule_order);
2810  this->comm().broadcast(optimal_qrule_order_int, proc_ID_index);
2811  optimal_qrule_order = static_cast<Order>(optimal_qrule_order_int);
2812  }
2813 
2814  libmesh_error_msg_if(optimal_elem_id == DofObject::invalid_id, "Error: Invalid element ID");
2815 
2816  if (add_basis_function)
2817  {
2818  if (optimal_value == 0.)
2819  {
2820  libMesh::out << "Encountered linearly dependent data in EIM enrichment, hence skip adding new basis function" << std::endl;
2821  return true;
2822  }
2823 
2824  // Scale local_pf so that its largest value is 1.0
2825  scale_parametrized_function(local_pf, 1./optimal_value);
2826 
2827  // Add local_pf as the new basis function and store data
2828  // associated with the interpolation point.
2829  eim_eval.add_basis_function(local_pf);
2830  }
2831 
2832  eim_eval.add_interpolation_data(optimal_point,
2833  optimal_comp,
2834  optimal_elem_id,
2835  optimal_subdomain_id,
2836  optimal_qp,
2837  optimal_point_perturbs,
2838  optimal_point_phi_i_qp,
2839  optimal_elem_type,
2840  optimal_JxW_all_qp,
2841  optimal_phi_i_all_qp,
2842  optimal_qrule_order,
2843  optimal_dxyzdxi_elem_center,
2844  optimal_dxyzdeta_elem_center);
2845 
2846  // In this case we did not encounter a linearly dependent basis function, so return false
2847  return false;
2848 }
2849 
2850 void RBEIMConstruction::update_eim_matrices(bool set_eim_error_indicator)
2851 {
2852  LOG_SCOPE("update_eim_matrices()", "RBEIMConstruction");
2853 
2854  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2855  unsigned int RB_size = eim_eval.get_n_basis_functions();
2856 
2857  libmesh_assert_msg(RB_size >= 1, "Must have at least 1 basis function.");
2858 
2859  if (set_eim_error_indicator)
2860  {
2861  // Here we have RB_size EIM basis functions, and RB_size+1 interpolation points,
2862  // since we should have added one extra interpolation point for the EIM error
2863  // indicator. As a result, we use RB_size as the index to access the (RB_size+1)^th
2864  // interpolation point in the calls to eim_eval.get_interpolation_points_*.
2865  DenseVector<Number> extra_point_row(RB_size);
2866 
2867  if (eim_eval.get_parametrized_function().on_mesh_sides())
2868  {
2869  // update the EIM interpolation matrix
2870  for (unsigned int j=0; j<RB_size; j++)
2871  {
2872  // Evaluate the basis functions at the new interpolation point in order
2873  // to update the interpolation matrix
2874  Number value =
2876  eim_eval.get_interpolation_points_elem_id(RB_size),
2877  eim_eval.get_interpolation_points_side_index(RB_size),
2878  eim_eval.get_interpolation_points_comp(RB_size),
2879  eim_eval.get_interpolation_points_qp(RB_size));
2880  extra_point_row(j) = value;
2881  }
2882  }
2883  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
2884  {
2885  // update the EIM interpolation matrix
2886  for (unsigned int j=0; j<RB_size; j++)
2887  {
2888  // Evaluate the basis functions at the new interpolation point in order
2889  // to update the interpolation matrix
2890  Number value =
2892  eim_eval.get_interpolation_points_node_id(RB_size),
2893  eim_eval.get_interpolation_points_comp(RB_size));
2894  extra_point_row(j) = value;
2895  }
2896  }
2897  else
2898  {
2899  // update the EIM interpolation matrix
2900  for (unsigned int j=0; j<RB_size; j++)
2901  {
2902  // Evaluate the basis functions at the new interpolation point in order
2903  // to update the interpolation matrix
2904  Number value =
2905  eim_eval.get_eim_basis_function_value(j,
2906  eim_eval.get_interpolation_points_elem_id(RB_size),
2907  eim_eval.get_interpolation_points_comp(RB_size),
2908  eim_eval.get_interpolation_points_qp(RB_size));
2909  extra_point_row(j) = value;
2910  }
2911  }
2912 
2913  eim_eval.set_error_indicator_interpolation_row(extra_point_row);
2914  return;
2915  }
2916 
2917  if (eim_eval.get_parametrized_function().on_mesh_sides())
2918  {
2919  // update the matrix that is used to evaluate L2 projections
2920  // into the EIM approximation space
2921  for (unsigned int i=(RB_size-1); i<RB_size; i++)
2922  {
2923  for (unsigned int j=0; j<RB_size; j++)
2924  {
2926  eim_eval.get_side_basis_function(i),
2927  /*apply_comp_scaling*/ false);
2928 
2930  if (i!=j)
2931  {
2932  // The inner product matrix is assumed to be hermitian
2934  }
2935  }
2936  }
2937 
2938  // update the EIM interpolation matrix
2939  for (unsigned int j=0; j<RB_size; j++)
2940  {
2941  // Evaluate the basis functions at the new interpolation point in order
2942  // to update the interpolation matrix
2943  Number value =
2945  eim_eval.get_interpolation_points_elem_id(RB_size-1),
2946  eim_eval.get_interpolation_points_side_index(RB_size-1),
2947  eim_eval.get_interpolation_points_comp(RB_size-1),
2948  eim_eval.get_interpolation_points_qp(RB_size-1));
2949  eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
2950  }
2951  }
2952  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
2953  {
2954  // update the matrix that is used to evaluate L2 projections
2955  // into the EIM approximation space
2956  for (unsigned int i=(RB_size-1); i<RB_size; i++)
2957  {
2958  for (unsigned int j=0; j<RB_size; j++)
2959  {
2961  eim_eval.get_node_basis_function(i),
2962  /*apply_comp_scaling*/ false);
2963 
2965  if (i!=j)
2966  {
2967  // The inner product matrix is assumed to be hermitian
2969  }
2970  }
2971  }
2972 
2973  // update the EIM interpolation matrix
2974  for (unsigned int j=0; j<RB_size; j++)
2975  {
2976  // Evaluate the basis functions at the new interpolation point in order
2977  // to update the interpolation matrix
2978  Number value =
2980  eim_eval.get_interpolation_points_node_id(RB_size-1),
2981  eim_eval.get_interpolation_points_comp(RB_size-1));
2982  eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
2983  }
2984  }
2985  else
2986  {
2987  // update the matrix that is used to evaluate L2 projections
2988  // into the EIM approximation space
2989  for (unsigned int i=(RB_size-1); i<RB_size; i++)
2990  {
2991  for (unsigned int j=0; j<RB_size; j++)
2992  {
2994  eim_eval.get_basis_function(i),
2995  /*apply_comp_scaling*/ false);
2996 
2998  if (i!=j)
2999  {
3000  // The inner product matrix is assumed to be hermitian
3002  }
3003  }
3004  }
3005 
3006  // update the EIM interpolation matrix
3007  for (unsigned int j=0; j<RB_size; j++)
3008  {
3009  // Evaluate the basis functions at the new interpolation point in order
3010  // to update the interpolation matrix
3011  Number value =
3012  eim_eval.get_eim_basis_function_value(j,
3013  eim_eval.get_interpolation_points_elem_id(RB_size-1),
3014  eim_eval.get_interpolation_points_comp(RB_size-1),
3015  eim_eval.get_interpolation_points_qp(RB_size-1));
3016  eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
3017  }
3018  }
3019 }
3020 
3022  Number scaling_factor)
3023 {
3024  for (auto & pr : local_pf)
3025  {
3026  auto & values = pr.second;
3027  for ( auto & value : values)
3028  value *= scaling_factor;
3029  }
3030 }
3031 
3032 unsigned int RBEIMConstruction::get_random_int_0_to_n(unsigned int n)
3033 {
3034  // std::random_device seed;
3035  // std::mt19937 gen{seed()};
3036  // We do not use a random seed here, since we generally prefer our results
3037  // to reproducible, rather than fully random. If desired we could provide an
3038  // option to use the random seed approach (commented out above).
3039  std::default_random_engine gen;
3040  std::uniform_int_distribution<> dist{0, static_cast<int>(n)};
3041  return dist(gen);
3042 }
3043 
3045 {
3046  EimPointData eim_point_data;
3047 
3048  // If we have more than one process, then we need to do a parallel union
3049  // of v to make sure that we have data from all processors. Our approach
3050  // here is to set v_ptr to either v or global_v, depending on whether we
3051  // are in parallel or serial. The purpose of this approach is to avoid
3052  // making a copy of v in the case that this is a serial job.
3053  QpDataMap const * v_ptr = nullptr;
3054  QpDataMap global_v;
3055  if (comm().size() > 1)
3056  {
3057  global_v = v;
3058 
3059  // We only use global_v on proc 0, so we set the second argument of
3060  // set_union() to zero here to indicate that we only need the result
3061  // on proc 0.
3062  comm().set_union(global_v, 0);
3063  v_ptr = &global_v;
3064  }
3065  else
3066  {
3067  v_ptr = &v;
3068  }
3069 
3070  bool error_finding_new_element = false;
3071  if (comm().rank() == 0)
3072  {
3073  const VectorizedEvalInput & vec_eval_input = get_rb_eim_evaluation().get_vec_eval_input();
3074 
3075  {
3076  std::set<dof_id_type> previous_elem_ids(vec_eval_input.elem_ids.begin(), vec_eval_input.elem_ids.end());
3077 
3078  // We ensure that we select a point that has not been selected previously
3079  // by setting up new_elem_ids to contain only elements that are not in
3080  // previous_elem_ids, and then selecting the elem_id at random from new_elem_ids.
3081  // We give an error if there are no elements in new_elem_ids. This is potentially
3082  // an overzealous assertion since we could pick an element that has already
3083  // been selected as long as we pick a (comp_index, qp_index) that has not already
3084  // been selected for that element.
3085  //
3086  // However, in general we do not expect all elements to be selected in the EIM
3087  // training, so it is reasonable to use the simple assertion below. Moreover, by
3088  // ensuring that we choose a new element we should typically ensure that the
3089  // randomly selected point has some separation from the previous EIM points, which
3090  // is typically desirable if we want EIM evaluations that are independent from
3091  // the EIM points (e.g. for EIM error indicator purposes).
3092  std::set<dof_id_type> new_elem_ids;
3093  for (const auto & v_pair : *v_ptr)
3094  if (previous_elem_ids.count(v_pair.first) == 0)
3095  new_elem_ids.insert(v_pair.first);
3096 
3097  // If new_elem_ids is empty then we set error_finding_new_element to true.
3098  // We then broadcast the value of error_finding_new_element to all processors
3099  // below in order to ensure that all processors agree on whether or not
3100  // there was an error.
3101  error_finding_new_element = (new_elem_ids.empty());
3102 
3103  if (!error_finding_new_element)
3104  {
3105  unsigned int random_elem_idx = get_random_int_0_to_n(new_elem_ids.size()-1);
3106 
3107  auto item = new_elem_ids.begin();
3108  std::advance(item, random_elem_idx);
3109  eim_point_data.elem_id = *item;
3110  }
3111  }
3112 
3113  if (!error_finding_new_element)
3114  {
3115  {
3116  const auto & vars_and_qps = libmesh_map_find(*v_ptr,eim_point_data.elem_id);
3117  eim_point_data.comp_index = get_random_int_0_to_n(vars_and_qps.size()-1);
3118  }
3119 
3120  {
3121  const auto & qps = libmesh_map_find(*v_ptr,eim_point_data.elem_id)[eim_point_data.comp_index];
3122  eim_point_data.qp_index = get_random_int_0_to_n(qps.size()-1);
3123  }
3124  }
3125  }
3126 
3127  comm().broadcast(error_finding_new_element);
3128  libmesh_error_msg_if(error_finding_new_element, "Could not find new element in get_random_point()");
3129 
3130  // Broadcast the values computed above from rank 0
3131  comm().broadcast(eim_point_data.elem_id);
3132  comm().broadcast(eim_point_data.comp_index);
3133  comm().broadcast(eim_point_data.qp_index);
3134 
3135  return eim_point_data;
3136 }
3137 
3139 {
3140  EimPointData eim_point_data;
3141 
3142  // If we have more than one process, then we need to do a parallel union
3143  // of v to make sure that we have data from all processors. Our approach
3144  // here is to set v_ptr to either v or global_v, depending on whether we
3145  // are in parallel or serial. The purpose of this approach is to avoid
3146  // making a copy of v in the case that this is a serial job.
3147  SideQpDataMap const * v_ptr = nullptr;
3148  SideQpDataMap global_v;
3149  if (comm().size() > 1)
3150  {
3151  global_v = v;
3152 
3153  // We only use global_v on proc 0, so we set the second argument of
3154  // set_union() to zero here to indicate that we only need the result
3155  // on proc 0.
3156  comm().set_union(global_v, 0);
3157  v_ptr = &global_v;
3158  }
3159  else
3160  {
3161  v_ptr = &v;
3162  }
3163 
3164  bool error_finding_new_element_and_side = false;
3165  if (comm().rank() == 0)
3166  {
3167  const VectorizedEvalInput & vec_eval_input = get_rb_eim_evaluation().get_vec_eval_input();
3168 
3169  std::pair<dof_id_type,unsigned int> elem_and_side;
3170  {
3171  std::set<std::pair<dof_id_type,unsigned int>> previous_elem_and_side_ids;
3172  for (const auto idx : index_range(vec_eval_input.elem_ids))
3173  {
3174  previous_elem_and_side_ids.insert(
3175  std::make_pair(vec_eval_input.elem_ids[idx],
3176  vec_eval_input.side_indices[idx]));
3177  }
3178 
3179  // See discussion above in the QpDataMap case for the justification
3180  // of how we set up new_elem_and_side_ids below.
3181  std::set<std::pair<dof_id_type,unsigned int>> new_elem_and_side_ids;
3182  for (const auto & v_pair : *v_ptr)
3183  if (previous_elem_and_side_ids.count(v_pair.first) == 0)
3184  new_elem_and_side_ids.insert(v_pair.first);
3185 
3186  // If new_elem_and_side_ids is empty then we set error_finding_new_element_and_side
3187  // to true. We then broadcast the value of error_finding_new_element_and_side to all
3188  // processors below in order to ensure that all processors agree on whether
3189  // or not there was an error.
3190  error_finding_new_element_and_side = (new_elem_and_side_ids.empty());
3191 
3192  if (!error_finding_new_element_and_side)
3193  {
3194  unsigned int random_elem_and_side_idx = get_random_int_0_to_n(new_elem_and_side_ids.size()-1);
3195 
3196  auto item = new_elem_and_side_ids.begin();
3197  std::advance(item, random_elem_and_side_idx);
3198  elem_and_side = *item;
3199  eim_point_data.elem_id = elem_and_side.first;
3200  eim_point_data.side_index = elem_and_side.second;
3201  }
3202  }
3203 
3204  if (!error_finding_new_element_and_side)
3205  {
3206  {
3207  const auto & vars_and_qps = libmesh_map_find(*v_ptr,elem_and_side);
3208  eim_point_data.comp_index = get_random_int_0_to_n(vars_and_qps.size()-1);
3209  }
3210 
3211  {
3212  const auto & qps = libmesh_map_find(*v_ptr,elem_and_side)[eim_point_data.comp_index];
3213  eim_point_data.qp_index = get_random_int_0_to_n(qps.size()-1);
3214  }
3215  }
3216  }
3217 
3218  comm().broadcast(error_finding_new_element_and_side);
3219  libmesh_error_msg_if(error_finding_new_element_and_side, "Could not find new (element,side) in get_random_point()");
3220 
3221  // Broadcast the values computed above from rank 0
3222  comm().broadcast(eim_point_data.elem_id);
3223  comm().broadcast(eim_point_data.side_index);
3224  comm().broadcast(eim_point_data.comp_index);
3225  comm().broadcast(eim_point_data.qp_index);
3226 
3227  return eim_point_data;
3228 }
3229 
3231 {
3232  EimPointData eim_point_data;
3233 
3234  // If we have more than one process, then we need to do a parallel union
3235  // of v to make sure that we have data from all processors. Our approach
3236  // here is to set v_ptr to either v or global_v, depending on whether we
3237  // are in parallel or serial. The purpose of this approach is to avoid
3238  // making a copy of v in the case that this is a serial job.
3239  NodeDataMap const * v_ptr = nullptr;
3240  NodeDataMap global_v;
3241  if (comm().size() > 1)
3242  {
3243  global_v = v;
3244 
3245  // We only use global_v on proc 0, so we set the second argument of
3246  // set_union() to zero here to indicate that we only need the result
3247  // on proc 0.
3248  comm().set_union(global_v, 0);
3249  v_ptr = &global_v;
3250  }
3251  else
3252  {
3253  v_ptr = &v;
3254  }
3255 
3256  bool error_finding_new_node = false;
3257  if (comm().rank() == 0)
3258  {
3259  const VectorizedEvalInput & vec_eval_input = get_rb_eim_evaluation().get_vec_eval_input();
3260 
3261  {
3262  std::set<dof_id_type> previous_node_ids(vec_eval_input.node_ids.begin(), vec_eval_input.node_ids.end());
3263 
3264  // See discussion above in the QpDataMap case for the justification
3265  // of how we set up new_node_ids below.
3266  std::set<dof_id_type> new_node_ids;
3267  for (const auto & v_pair : *v_ptr)
3268  if (previous_node_ids.count(v_pair.first) == 0)
3269  new_node_ids.insert(v_pair.first);
3270 
3271  // If new_node_ids is empty then we set error_finding_new_node
3272  // to true. We then broadcast the value of error_finding_new_node to all
3273  // processors below in order to ensure that all processors agree on whether
3274  // or not there was an error.
3275  error_finding_new_node = (new_node_ids.empty());
3276 
3277  if (!error_finding_new_node)
3278  {
3279  unsigned int random_node_idx = get_random_int_0_to_n(new_node_ids.size()-1);
3280 
3281  auto item = new_node_ids.begin();
3282  std::advance(item, random_node_idx);
3283  eim_point_data.node_id = *item;
3284  }
3285  }
3286 
3287  if (!error_finding_new_node)
3288  {
3289  const auto & vars = libmesh_map_find(*v_ptr,eim_point_data.node_id);
3290  eim_point_data.comp_index = get_random_int_0_to_n(vars.size()-1);
3291  }
3292  }
3293 
3294  comm().broadcast(error_finding_new_node);
3295  libmesh_error_msg_if(error_finding_new_node, "Could not find new node in get_random_point()");
3296 
3297  // Broadcast the values computed above from rank 0
3298  comm().broadcast(eim_point_data.node_id);
3299  comm().broadcast(eim_point_data.comp_index);
3300 
3301  return eim_point_data;
3302 }
3303 
3305 {
3306  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
3307 
3308  if (eim_eval.get_parametrized_function().on_mesh_sides())
3310  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
3312  else
3314 }
3315 
3316 } // namespace libMesh
virtual unsigned int get_n_components() const =0
Specify the number of components in this parametrized function.
ElemType
Defines an enum for geometric element types.
Real get_max_abs_value(const DataMap &v) const
Get the maximum absolute value from a vector stored in the format that we use for basis functions...
virtual void preevaluate_parametrized_function_on_mesh_sides(const RBParameters &mu, const std::map< std::pair< dof_id_type, unsigned int >, std::vector< Point >> &side_all_xyz, const std::map< std::pair< dof_id_type, unsigned int >, subdomain_id_type > &sbd_ids, const std::map< std::pair< dof_id_type, unsigned int >, boundary_id_type > &side_boundary_ids, const std::map< std::pair< dof_id_type, unsigned int >, unsigned int > &side_types, const std::map< std::pair< dof_id_type, unsigned int >, std::vector< std::vector< Point >> > &side_all_xyz_perturb, const System &sys)
Same as preevaluate_parametrized_function_on_mesh() except for mesh sides.
This is the EquationSystems class.
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...
Order
defines an enum for polynomial orders.
Definition: enum_order.h:40
bool quiet_mode
Flag to indicate whether we print out extra information during the Offline stage. ...
A Node is like a Point, but with more information.
Definition: node.h:52
Real get_max_abs_value_in_training_set() const
Get the maximum value (across all processors) from the parametrized functions in the training set...
std::unordered_map< dof_id_type, boundary_id_type > _local_node_boundary_ids
void get_side_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for edge/face (2D/3D) finite element object for variable var for the largest dimension in th...
Definition: fem_context.h:317
virtual void pre_fe_reinit(const System &, const Elem *e)
Reinitializes local data vectors/matrices on the current geometric element.
Definition: fem_context.C:1683
boost::multiprecision::float128 real(const boost::multiprecision::float128 in)
const SideQpDataMap & get_side_basis_function(unsigned int i) const
Get a reference to the i^th side basis function.
void add_basis_function(const QpDataMap &bf)
Add bf to our EIM basis.
bool enrich_eim_approximation_on_nodes(const NodeDataMap &node_pf, bool add_basis_function, EimPointData *eim_point_data)
Implementation of enrich_eim_approximation() for the case of element nodes.
T libmesh_conj(T a)
void set_interpolation_matrix_entry(unsigned int i, unsigned int j, Number value)
Set entry of the EIM interpolation matrix.
std::map< dof_id_type, std::vector< Number > > NodeDataMap
Type of the data structure used to map from (node id) -> [n_vars] data.
void initialize_eim_construction()
Perform initialization of this object to prepare for running train_eim_approximation().
bool _normalize_solution_snapshots
Set this boolean to true if we want to normalize solution snapshots used in training to have norm of ...
RBEIMEvaluation & get_rb_eim_evaluation()
Get a reference to the RBEvaluation object.
const std::set< unsigned int > & scale_components_in_enrichment() const
Get _scale_components_in_enrichment.
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 ...
void enable_set_Nmax_from_n_snapshots(int increment)
Call this method to set _set_Nmax_from_n_snapshots=true and _Nmax_from_n_snapshots_increment=incremen...
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.
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.
static constexpr Real TOLERANCE
unsigned char get_elem_dim() const
Definition: fem_context.h:944
const Elem & get_elem() const
Accessor for current Elem object.
Definition: fem_context.h:908
virtual void reinit()
Reinitializes degrees of freedom and other required data on the current mesh.
Definition: system.C:445
unsigned int dim
static Point inverse_map(const unsigned int dim, const Elem *elem, const Point &p, const Real tolerance=TOLERANCE, const bool secure=true, const bool extra_checks=true)
Definition: fe_map.C:1628
virtual Real train_eim_approximation()
Generate the EIM approximation for the specified parametrized function using either POD or the Greedy...
std::vector< SideQpDataMap > _local_side_parametrized_functions_for_training
Same as _local_parametrized_functions_for_training except for side data.
Real get_parameter_min(const std::string &param_name) const
Get minimum allowable value of parameter param_name.
unsigned int get_n_basis_functions() const
Return the current number of EIM basis functions.
void add_node_basis_function(const NodeDataMap &node_bf)
Add node_bf to our EIM basis.
Number side_inner_product(const SideQpDataMap &v, const SideQpDataMap &w, bool apply_comp_scaling)
Same as inner_product() except for side data.
Real get_parameter_max(const std::string &param_name) const
Get maximum allowable value of parameter param_name.
std::map< std::pair< dof_id_type, unsigned int >, unsigned int > _local_side_quad_point_side_types
For side data, we also store "side type" info.
const RBParameters & get_parameters_max() const
Get an RBParameters object that specifies the maximum allowable value for each parameter.
std::unordered_map< dof_id_type, std::vector< Real > > _local_quad_point_JxW
void sum(T &r) const
This struct is used to encapsulate the arguments required to specify an EIM point that we may add to ...
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
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 set_quiet_mode(bool quiet_mode_in)
Set the quiet_mode flag.
const std::vector< DenseVector< Number > > & get_rb_eim_solutions() const
Return the EIM solution coefficients from the most recent call to rb_eim_solves().
virtual void set_best_fit_type_flag(const std::string &best_fit_type_string)
Specify which type of "best fit" we use to guide the EIM greedy algorithm.
void reinit_eim_projection_matrix()
Zero the _eim_projection_matrix and resize it to be get_Nmax() x get_Nmax().
virtual void process_parameters_file(const std::string &parameters_filename)
Read parameters in from file and set up this system accordingly.
std::vector< dof_id_type > elem_ids
Real _max_abs_value_in_training_set
Maximum value in _local_parametrized_functions_for_training across all processors.
const Parallel::Communicator & comm() const
bool is_quiet() const
Is the system in quiet mode?
RBEIMEvaluation * _rb_eim_eval
The RBEIMEvaluation object that we use to perform the EIM training.
const QpDataMap & get_parametrized_function_from_training_set(unsigned int training_index) const
Get a const reference to the specified parametrized function from the training set.
RBEIMConstruction(EquationSystems &es, const std::string &name, const unsigned int number)
Constructor.
unsigned int get_interpolation_points_side_index(unsigned int index) const
virtual void clear() override
Clear this object.
void decrement_vector(QpDataMap &v, const DenseVector< Number > &coeffs)
Subtract coeffs[i]*basis_function[i] from v.
EimPointData get_random_point(const QpDataMap &v)
Helper function that identifies a random EIM point from v.
void set_training_parameter_values(const std::string &param_name, const std::vector< RBParameter > &values)
Overwrite the local training samples for param_name using values.
const NodeDataMap & get_node_basis_function(unsigned int i) const
Get a reference to the i^th node basis function.
The libMesh namespace provides an interface to certain functionality in the library.
unsigned int get_interpolation_points_comp(unsigned int index) const
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.
void set_abs_training_tolerance(Real new_training_tolerance)
Get/set the absolute tolerance for the basis training.
void update_eim_matrices(bool set_eim_error_indicator)
Update the matrices used in training the EIM approximation.
virtual Real train_eim_approximation_with_greedy()
Generate the EIM approximation for the specified parametrized function using the Greedy Algorithm...
const MeshBase & get_mesh() const
Definition: system.h:2354
Number node_inner_product(const NodeDataMap &v, const NodeDataMap &w, bool apply_comp_scaling)
Same as inner_product() except for node data.
Define a struct for the input to the "vectorized evaluate" functions below.
virtual void set_Nmax(unsigned int Nmax)
void iota(ForwardIter first, ForwardIter last, T value)
Utility::iota was created back when std::iota was just an SGI STL extension.
Definition: utility.h:229
virtual void preevaluate_parametrized_function_on_mesh(const RBParameters &mu, const std::unordered_map< dof_id_type, std::vector< Point >> &all_xyz, const std::unordered_map< dof_id_type, subdomain_id_type > &sbd_ids, const std::unordered_map< dof_id_type, std::vector< std::vector< Point >> > &all_xyz_perturb, const System &sys)
Store the result of vectorized_evaluate.
void add_side_basis_function(const SideQpDataMap &side_bf)
Add side_bf to our EIM basis.
virtual Number lookup_preevaluated_value_on_mesh(unsigned int comp, dof_id_type elem_id, unsigned int qp) const
Look up the preevaluate values of the parametrized function for component comp, element elem_id...
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.
This is the MeshBase class.
Definition: mesh_base.h:75
virtual void print_info()
Print out info that describes the current setup of this RBConstruction.
void side_decrement_vector(SideQpDataMap &v, const DenseVector< Number > &coeffs)
Same as decrement_vector() except for Side data.
std::map< std::pair< dof_id_type, unsigned int >, subdomain_id_type > _local_side_quad_point_subdomain_ids
virtual void elem_fe_reinit(const std::vector< Point > *const pts=nullptr)
Reinitializes interior FE objects on the current geometric element.
Definition: fem_context.C:1477
virtual void load_training_set(const std::map< std::string, std::vector< RBParameter >> &new_training_set)
Overwrite the training parameters with new_training_set.
Real get_node_max_abs_value(const NodeDataMap &v) const
Get the maximum absolute value from a vector stored in the format that we use for basis functions...
const std::unordered_map< dof_id_type, std::vector< Real > > & get_local_quad_point_JxW()
Get the interior and side quadrature weights.
virtual void initialize_training_parameters(const RBParameters &mu_min, const RBParameters &mu_max, const unsigned int n_global_training_samples, const std::map< std::string, bool > &log_param_scale, const bool deterministic=true)
Initialize the parameter ranges and indicate whether deterministic or random training parameters shou...
void set_training_random_seed(int seed)
Set the seed that is used to randomly generate training parameters.
unsigned int n_parameters() const
Get the number of parameters that have been added.
T pow(const T &x)
Definition: utility.h:328
bool is_lookup_table
Boolean to indicate if this parametrized function is defined based on a lookup table or not...
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...
std::vector< unsigned int > side_indices
const QpDataMap & get_basis_function(unsigned int i) const
Get a reference to the i^th basis function.
std::vector< QpDataMap > _local_parametrized_functions_for_training
The parametrized functions that are used for training.
int8_t boundary_id_type
Definition: id_types.h:51
static const boundary_id_type invalid_id
Number used for internal use.
const RBParameters & get_parameters_min() const
Get an RBParameters object that specifies the minimum allowable value for each parameter.
virtual T el(const unsigned int i, const unsigned int j) const override final
Definition: dense_matrix.h:126
const System & get_system() const
Accessor for associated system.
Definition: diff_context.h:106
std::vector< NodeDataMap > _local_node_parametrized_functions_for_training
Same as _local_parametrized_functions_for_training except for node data.
void resize_data_structures(const unsigned int Nmax)
Resize the data structures for storing data associated with this object.
void svd(DenseVector< Real > &sigma)
Compute the singular value decomposition of the matrix.
const boundary_id_type node_boundary_id
unsigned int _Nmax
Maximum number of EIM basis functions we are willing to use.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition: system.h:96
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.
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.
std::map< std::pair< dof_id_type, unsigned int >, std::vector< Real > > _local_side_quad_point_JxW
virtual Number lookup_preevaluated_side_value_on_mesh(unsigned int comp, dof_id_type elem_id, unsigned int side_index, unsigned int qp) const
Look up the preevaluated values of the parametrized function for component comp, element elem_id...
virtual void init_context(FEMContext &)
Pre-request FE data needed for calculations.
numeric_index_type get_local_n_training_samples() const
Get the total number of training samples local to this processor.
libmesh_assert(ctx)
void store_eim_solutions_for_training_set()
Get the EIM solution vector at all parametrized functions in the training set.
static const dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:482
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.
Order get_order() const
Definition: quadrature.h:249
This class provides all data required for a physics package (e.g.
Definition: fem_context.h:62
const Elem * reference_elem() const
Definition: elem.C:441
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 node_decrement_vector(NodeDataMap &v, const DenseVector< Number > &coeffs)
Same as decrement_vector() except for node data.
const RBParameters & get_parameters() const
Get the current parameters.
void print_parameters() const
Print the current parameters.
unsigned int _max_abs_value_in_training_set_index
The training sample index at which we found _max_abs_value_in_training_set.
bool requires_all_elem_qp_data
Boolean to indicate whether this parametrized function requires data from all qps on the current elem...
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.
void maxloc(T &r, unsigned int &max_id) const
This class is part of the rbOOmit framework.
Definition: rb_parameters.h:52
bool requires_all_elem_center_data
Boolean to indicate whether this parametrized function requires data from the center on the current e...
void apply_normalization_to_solution_snapshots()
Rescale solution snapshots so that they all have unity norm.
numeric_index_type get_n_training_samples() const
Get the number of global training samples.
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
virtual std::unique_ptr< ElemAssembly > build_eim_assembly(unsigned int bf_index)=0
Build an element assembly object that will access basis function bf_index.
BEST_FIT_TYPE best_fit_type_flag
Enum that indicates which type of "best fit" algorithm we should use.
DenseMatrix< Number > _eim_projection_matrix
The matrix we use in order to perform L2 projections of parametrized functions as part of EIM trainin...
EimPointData get_random_point_from_training_sample()
Get a random point using the 0^th training sample as input to get_random_point(). ...
static void scale_parametrized_function(DataMap &local_pf, Number scaling_factor)
Scale all values in pf by scaling_factor.
void set_params_from_training_set(unsigned int global_index)
Set parameters to the RBParameters stored in index global_index of the global training set...
virtual void preevaluate_parametrized_function_on_mesh_nodes(const RBParameters &mu, const std::unordered_map< dof_id_type, Point > &all_xyz, const std::unordered_map< dof_id_type, boundary_id_type > &node_boundary_ids, const System &sys)
Same as preevaluate_parametrized_function_on_mesh() except for mesh nodes.
virtual unsigned int n_sides() const =0
bool _set_Nmax_from_n_snapshots
If _set_Nmax_from_n_snapshots=true, then we overrule Nmax to be Nmax += _Nmax_from_n_snapshots_increm...
bool set_parameters(const RBParameters &params)
Set the current parameters to params The parameters are checked for validity; an error is thrown if t...
dof_id_type get_interpolation_points_node_id(unsigned int index) const
virtual bool use_eim_error_indicator() const
Virtual function to indicate if we use the EIM error indicator in this case.
void set_rb_eim_evaluation(RBEIMEvaluation &rb_eim_eval_in)
Set the RBEIMEvaluation object.
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2520
DenseVector< Number > rb_eim_solve(DenseVector< Number > &EIM_rhs)
Calculate the EIM approximation for the given right-hand side vector EIM_rhs.
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
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.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::map< std::pair< dof_id_type, unsigned int >, boundary_id_type > _local_side_quad_point_boundary_ids
void initialize_rb_property_map()
Initialize the rb_property_map of RBEIMEvaluation (this) from the rb_property_map stored in RBParamet...
std::unordered_map< dof_id_type, Point > _local_node_locations
Same as above except for node data.
const VectorizedEvalInput & get_vec_eval_input() const
Get the VectorizedEvalInput data.
const SideQpDataMap & get_side_parametrized_function_from_training_set(unsigned int training_index) const
virtual void initialize_eim_assembly_objects()
Build a vector of ElemAssembly objects that accesses the basis functions stored in this RBEIMConstruc...
unsigned char side
Current side for side_* to examine.
Definition: fem_context.h:1013
void max(const T &r, T &o, Request &req) const
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.
virtual unsigned short dim() const =0
void set_rel_training_tolerance(Real new_training_tolerance)
Get/set the relative tolerance for the basis training.
std::vector< std::unique_ptr< ElemAssembly > > & get_eim_assembly_objects()
void set_value(const std::string &param_name, Real value)
Set the value of the specified parameter.
static Point map(const unsigned int dim, const Elem *elem, const Point &reference_point)
Definition: fe_map.C:2069
Number inner_product(const QpDataMap &v, const QpDataMap &w, bool apply_comp_scaling)
Evaluate the inner product of vec1 and vec2 which specify values at quadrature points.
OStreamProxy out
void set_error_indicator_interpolation_row(const DenseVector< Number > &error_indicator_row)
bool enrich_eim_approximation_on_sides(const SideQpDataMap &side_pf, bool add_basis_function, EimPointData *eim_point_data)
Implementation of enrich_eim_approximation() for the case of element sides.
static const bool value
Definition: xdr_io.C:54
virtual const Elem & elem_ref(const dof_id_type i) const
Definition: mesh_base.h:634
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Definition: dense_matrix.h:895
const std::map< std::pair< dof_id_type, unsigned int >, std::vector< Real > > & get_local_side_quad_point_JxW()
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:140
void initialize_parametrized_functions_in_training_set()
Compute and store the parametrized function for each parameter in the training set at all the stored ...
dof_id_type get_interpolation_points_elem_id(unsigned int index) const
unsigned int get_Nmax() const
Get/set Nmax, the maximum number of RB functions we are willing to compute.
void disable_set_Nmax_from_n_snapshots()
Call this method to set _set_Nmax_from_n_snapshots=false and reset _Nmax_from_n_snapshots_increment t...
std::pair< Real, unsigned int > compute_max_eim_error()
Find the training sample that has the largest EIM approximation error based on the current EIM approx...
RBEIMEvaluation::NodeDataMap NodeDataMap
Type of the data structure used to map from node id -> [n_vars] data.
bool serial_training_set
This boolean flag indicates whether or not the training set should be the same on all processors...
virtual Number lookup_preevaluated_node_value_on_mesh(unsigned int comp, dof_id_type node_id) const
Look up the preevaluate values of the parametrized function for component comp, node node_id...
RBParameters get_params_from_training_set(unsigned int global_index)
Return the RBParameters in index global_index of the global training set.
This class is part of the rbOOmit framework.
void get_element_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for interior finite element object for variable var for the largest dimension in the mesh...
Definition: fem_context.h:277
std::vector< Real > _component_scaling_in_training_set
Keep track of a scaling factor for each component of the parametrized functions in the training set w...
bool enrich_eim_approximation_on_interiors(const QpDataMap &interior_pf, bool add_basis_function, EimPointData *eim_point_data)
Implementation of enrich_eim_approximation() for the case of element interiors.
void enrich_eim_approximation(unsigned int training_index, bool add_basis_function, EimPointData *eim_point_data)
Add a new basis function to the EIM approximation.
Real _rel_training_tolerance
Relative and absolute tolerances for training the EIM approximation.
RBEIMEvaluation::QpDataMap QpDataMap
Type of the data structure used to map from (elem id) -> [n_vars][n_qp] data.
unsigned int get_n_parametrized_functions_for_training() const
Get the number of parametrized functions used for training.
Defines a dense vector for use in Finite Element-type computations.
Definition: dof_map.h:73
virtual Real train_eim_approximation_with_POD()
Generate the EIM approximation for the specified parametrized function using Proper Orthogonal Decomp...
const std::string & name() const
Definition: system.h:2338
std::unordered_map< dof_id_type, std::vector< Point > > _local_quad_point_locations
The quadrature point locations, quadrature point weights (JxW), and subdomain IDs on every element lo...
unsigned int n_vars() const
Definition: system.h:2426
const NodeDataMap & get_node_parametrized_function_from_training_set(unsigned int training_index) const
std::map< std::pair< dof_id_type, unsigned int >, std::vector< Point > > _local_side_quad_point_locations
Same as above except for side data.
RBParametrizedFunction & get_parametrized_function()
Get a reference to the parametrized function.
virtual void initialize_lookup_table()
If this parametrized function is defined based on a lookup table then we can call this function to in...
void initialize_parameters(const RBParameters &mu_min_in, const RBParameters &mu_max_in, const std::map< std::string, std::vector< Real >> &discrete_parameter_values)
Initialize the parameter ranges and set current_parameters.
std::vector< std::unique_ptr< ElemAssembly > > _rb_eim_assembly_objects
The vector of assembly objects that are created to point to this RBEIMConstruction.
RBEIMEvaluation::SideQpDataMap SideQpDataMap
Type of the data structure used to map from (elem id,side_index) -> [n_vars][n_qp] data...
std::map< std::pair< dof_id_type, unsigned int >, std::vector< std::vector< Point > > > _local_side_quad_point_locations_perturbations
This class enables evaluation of an Empirical Interpolation Method (EIM) approximation.
processor_id_type processor_id() const
Definition: dof_object.h:905
void initialize_qp_data()
Initialize the data associated with each quad point (location, JxW, etc.) so that we can use this in ...
std::unordered_map< dof_id_type, std::vector< std::vector< Point > > > _local_quad_point_locations_perturbations
EIM approximations often arise when applying a geometric mapping to a Reduced Basis formulation...
virtual ElemType type() const =0
unsigned int get_n_params() const
Get the number of parameters.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
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:117
unsigned int get_interpolation_points_qp(unsigned int index) const
const QBase & get_element_qrule() const
Accessor for element interior quadrature rule for the dimension of the current _elem.
Definition: fem_context.h:802
static void scale_node_parametrized_function(NodeDataMap &local_pf, Number scaling_factor)
Scale all values in pf by scaling_factor The templated function above handles the elem and side cases...
virtual void clear()
Clear all the data structures associated with the system.
bool is_discrete_parameter(const std::string &mu_name) const
Is parameter mu_name discrete?
Point vertex_average() const
Definition: elem.C:559
void set_rb_construction_parameters(unsigned int n_training_samples_in, bool deterministic_training_in, int training_parameters_random_seed_in, bool quiet_mode_in, unsigned int Nmax_in, Real rel_training_tolerance_in, Real abs_training_tolerance_in, const RBParameters &mu_min_in, const RBParameters &mu_max_in, const std::map< std::string, std::vector< Real >> &discrete_parameter_values_in, const std::map< std::string, bool > &log_scaling, std::map< std::string, std::vector< RBParameter >> *training_sample_list=nullptr)
Set the state of this RBConstruction object based on the arguments to this function.
const std::set< unsigned char > & elem_dimensions() const
Definition: fem_context.h:951
std::vector< dof_id_type > node_ids
unsigned int idx(const ElemType type, const unsigned int nx, const unsigned int i, const unsigned int j)
A useful inline function which replaces the macros used previously.
std::unordered_map< dof_id_type, subdomain_id_type > _local_quad_point_subdomain_ids
void print_discrete_parameter_values() const
Print out all the discrete parameter values.
static unsigned int get_random_int_0_to_n(unsigned int n)
Static helper function that is used by get_random_point().
uint8_t dof_id_type
Definition: id_types.h:67
void set_union(T &data, const unsigned int root_id) const
Real fd_delta
The finite difference step size in the case that this function in the case that this function uses fi...
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.