libMesh
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 
455  {
456  std::cout << "Maximum absolute value in the training set is "
457  << _max_abs_value_in_training_set << ", which is less than or equal to the abs. training tolerance of "
458  << get_abs_training_tolerance() << ", hence exiting Greedy basis enrichment with empty basis" << std::endl;
459  return 0.0;
460  }
461 
462  // Initialize greedy_error so that we do not incorrectly set is_zero_bf=true on
463  // the first iteration.
464  Real greedy_error = -1.;
465  std::vector<RBParameters> greedy_param_list;
466 
467  // Initialize the current training index to the index that corresponds
468  // to the largest (in terms of infinity norm) function in the training set.
469  // We do this to ensure that the first EIM basis function is not zero.
470  unsigned int current_training_index = _max_abs_value_in_training_set_index;
471  set_params_from_training_set(current_training_index);
472 
473  // We use this boolean to indicate if we will run one more iteration
474  // before exiting the loop below. We use this when computing the EIM
475  // error indicator, which requires one extra EIM iteration.
476  bool exit_on_next_iteration = false;
477 
478  // We also initialize a boolean to keep track of whether we have
479  // reached "n_samples" EIM basis functions, since we need to
480  // handle the EIM error indicator in a special way in this case.
481  bool bfs_equals_n_samples = false;
482 
483  while (true)
484  {
486  {
487  libMesh::out << "Number of basis functions (" << rbe.get_n_basis_functions()
488  << ") equals number of training samples." << std::endl;
489 
490  bfs_equals_n_samples = true;
491 
492  // If exit_on_next_iteration==true then we don't exit yet, since
493  // we still need to add data for the error indicator before exiting.
494  if (!exit_on_next_iteration)
495  break;
496  }
497 
498  libMesh::out << "Greedily selected parameter vector:" << std::endl;
500  greedy_param_list.emplace_back(get_parameters());
501 
502  libMesh::out << "Enriching the EIM approximation" << std::endl;
503  libmesh_try
504  {
505  bool is_zero_bf = bfs_equals_n_samples || (greedy_error == 0.);
506 
507  // If is_zero_bf==true then we add an "extra point" because we
508  // cannot add a usual EIM interpolation point in that case since
509  // the full EIM space is already covered. This is necessary when we
510  // want to add an extra point for error indicator purposes in the
511  // is_zero_bf==true case, for example.
512  std::unique_ptr<EimPointData> eim_point_data;
513  if (is_zero_bf)
514  eim_point_data = std::make_unique<EimPointData>(get_random_point_from_training_sample());
515 
516  // If exit_on_next_iteration==true then we do not add a basis function in
517  // that case since in that case we only need to add data for the EIM error
518  // indicator.
519  enrich_eim_approximation(current_training_index,
520  /*add_basis_function*/ !exit_on_next_iteration,
521  eim_point_data.get());
522  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
523 
524  libMesh::out << std::endl << "---- Basis dimension: "
525  << rbe.get_n_basis_functions() << " ----" << std::endl;
526 
527  if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table &&
529  {
530  // If this is a lookup table and we're using "EIM best fit" then we
531  // need to update the eim_solutions after each EIM enrichment so that
532  // we can call rb_eim_eval.rb_eim_solve() from within compute_max_eim_error().
534  }
535 
536  libMesh::out << "Computing EIM error on training set" << std::endl;
537  std::tie(greedy_error, current_training_index) = compute_max_eim_error();
538  set_params_from_training_set(current_training_index);
539 
540  libMesh::out << "Maximum EIM error is " << greedy_error << std::endl << std::endl;
541  }
542 #ifdef LIBMESH_ENABLE_EXCEPTIONS
543  catch (const std::exception & e)
544  {
545  // If we hit an exception when performing the enrichment for the error indicator, then
546  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
547  if (exit_on_next_iteration)
548  {
549  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
550  break;
551  }
552  else
553  throw;
554  }
555 #endif
556 
557  if (exit_on_next_iteration)
558  {
559  libMesh::out << "Extra EIM iteration for error indicator is complete, hence exiting EIM training now" << std::endl;
560  break;
561  }
562 
563  // Convergence and/or termination tests
564  {
565  bool exit_condition_satisfied = false;
566 
567  if (rbe.get_n_basis_functions() >= this->get_Nmax())
568  {
569  libMesh::out << "Maximum number of basis functions reached: Nmax = "
570  << get_Nmax() << std::endl;
571  exit_condition_satisfied = true;
572  }
573 
574  // We consider the relative tolerance as relative to the maximum value in the training
575  // set, since we assume that this maximum value provides a relevant scaling.
576  if (!exit_condition_satisfied)
578  {
579  libMesh::out << "Relative error tolerance reached." << std::endl;
580  exit_condition_satisfied = true;
581  }
582 
583  if (!exit_condition_satisfied)
584  if (greedy_error < get_abs_training_tolerance())
585  {
586  libMesh::out << "Absolute error tolerance reached." << std::endl;
587  exit_condition_satisfied = true;
588  }
589 
590  bool has_parameters = (get_parameters().n_parameters() > 0);
591  if (!exit_condition_satisfied)
592  {
593  bool do_exit = false;
594  // In the check for repeated parameters we have to make sure this isn't a case
595  // with no parameters, since in that case we would always report repeated
596  // parameters.
597  for (auto & param : greedy_param_list)
598  if (param == get_parameters() && has_parameters)
599  {
600  libMesh::out << "Exiting greedy because the same parameters were selected twice"
601  << std::endl;
602  do_exit = true;
603  break;
604  }
605 
606  if (do_exit)
607  exit_condition_satisfied = true;
608  }
609 
610  if (exit_condition_satisfied)
611  {
612  // If we're using the EIM error indicator then we need to run
613  // one extra EIM iteration since we use the extra EIM point
614  // to obtain our error indicator. If we're not using the EIM
615  // error indicator, then we just exit now.
616  if (get_rb_eim_evaluation().use_eim_error_indicator() && has_parameters)
617  {
618  exit_on_next_iteration = true;
619  libMesh::out << "EIM error indicator is active, hence we will run one extra EIM iteration before exiting"
620  << std::endl;
621  }
622  else
623  break;
624  }
625  }
626  } // end while(true)
627 
630  {
631  // We only enter here if best_fit_type_flag != EIM_BEST_FIT because we
632  // already called this above in the EIM_BEST_FIT case.
634  }
635 
636  return greedy_error;
637 }
638 
640 {
641  LOG_SCOPE("apply_normalization_to_solution_snapshots()", "RBEIMConstruction");
642 
643  libMesh::out << "Normalizing solution snapshots" << std::endl;
644 
645  bool apply_comp_scaling = !get_rb_eim_evaluation().scale_components_in_enrichment().empty();
646  unsigned int n_snapshots = get_n_training_samples();
648 
649  for (unsigned int i=0; i<n_snapshots; i++)
650  {
651  Real norm_val = 0.;
653  {
654  norm_val = std::sqrt(std::real(side_inner_product(
657  apply_comp_scaling)));
658 
659  if (norm_val > 0.)
661  }
662  else if (rbe.get_parametrized_function().on_mesh_nodes())
663  {
664  norm_val = std::sqrt(std::real(node_inner_product(
667  apply_comp_scaling)));
668 
669  if (norm_val > 0.)
671  }
672  else
673  {
674  norm_val = std::sqrt(std::real(inner_product(
677  apply_comp_scaling)));
678 
679  if (norm_val > 0.)
681  }
682 
683  // Since we're rescaling the training samples, we should also rescale
684  // _max_abs_value_in_training_set in the same way. We obtained the
685  // _max_abs_value_in_training_set value from the sample with index
686  // _max_abs_value_in_training_set_index, so we rescale using the norm
687  // of this sample here. Note that the value we obtain may not be exactly
688  // equal to the max value we would obtain after looping over all the
689  // normalized samples, because we normalized in the L2 norm rather than
690  // in the max norm, but it should be close to this "true max" value. The
691  // main use-case of _max_abs_value_in_training_set is to scale the relative
692  // error tolerance in the Greedy training, and the L2-norm-scaled value of
693  // _max_abs_value_in_training_set that we obtain here should be sufficient
694  // for that purpose.
695  if ((i == _max_abs_value_in_training_set_index) && (norm_val > 0.))
696  _max_abs_value_in_training_set /= norm_val;
697  }
698 
699  libMesh::out << "Maximum absolute value in the training set after normalization: "
700  << _max_abs_value_in_training_set << std::endl << std::endl;
701 }
702 
704 {
705  LOG_SCOPE("train_eim_approximation_with_POD()", "RBEIMConstruction");
706 
708 
709  unsigned int n_snapshots = get_n_training_samples();
710 
711  // If _set_Nmax_from_n_snapshots=true, then we overrule Nmax.
713  {
714  int updated_Nmax = (static_cast<int>(n_snapshots) + _Nmax_from_n_snapshots_increment);
715 
716  // We only overrule _Nmax if updated_Nmax is positive, since if Nmax=0 then we'll skip
717  // training here entirely, which is typically not what we want.
718  if (updated_Nmax > 0)
719  _Nmax = static_cast<unsigned int>(updated_Nmax);
720  }
721 
722  // _eim_projection_matrix is not used in the POD case, but we resize it here in any case
723  // to be consistent with what we do in train_eim_approximation_with_greedy().
724  // We need space for one extra interpolation point if we're using the
725  // EIM error indicator.
726  unsigned int max_matrix_size = rbe.use_eim_error_indicator() ? get_Nmax()+1 : get_Nmax();
727  _eim_projection_matrix.resize(max_matrix_size,max_matrix_size);
728 
729  rbe.initialize_parameters(*this);
731 
732  libmesh_error_msg_if(rbe.get_n_basis_functions() > 0,
733  "Error: We currently only support EIM training starting from an empty basis");
734 
735  libMesh::out << std::endl << "---- Performing POD EIM basis enrichment ----" << std::endl;
736 
737  // Set up the POD "correlation matrix". This enables us to compute the POD via the
738  // "method of snapshots", in which we compute a low rank representation of the
739  // n_snapshots x n_snapshots matrix.
740  DenseMatrix<Number> correlation_matrix(n_snapshots,n_snapshots);
741 
742  std::cout << "Start computing correlation matrix" << std::endl;
743 
744  bool apply_comp_scaling = !get_rb_eim_evaluation().scale_components_in_enrichment().empty();
745  for (unsigned int i=0; i<n_snapshots; i++)
746  {
747  for (unsigned int j=0; j<=i; j++)
748  {
749  Number inner_prod = 0.;
751  {
752  inner_prod = side_inner_product(
755  apply_comp_scaling);
756  }
757  else if (rbe.get_parametrized_function().on_mesh_nodes())
758  {
759  inner_prod = node_inner_product(
762  apply_comp_scaling);
763  }
764  else
765  {
766  inner_prod = inner_product(
769  apply_comp_scaling);
770  }
771 
772 
773  correlation_matrix(i,j) = inner_prod;
774  if(i != j)
775  {
776  correlation_matrix(j,i) = libmesh_conj(inner_prod);
777  }
778  }
779 
780  // Print out every 10th row so that we can see the progress
781  if ( (i+1) % 10 == 0)
782  std::cout << "Finished row " << (i+1) << " of " << n_snapshots << std::endl;
783  }
784  std::cout << "Finished computing correlation matrix" << std::endl;
785 
786  // Compute SVD of correlation matrix.
787  // Let Y = U S V^T, then the SVD below corresponds
788  // to Y^T Y = V S U^T U S V^T = V S^2 V^T.
789  // The POD basis we use is then given by U, which
790  // we can compute via U = Y V S^{-1}, which is what
791  // we compute below.
792  //
793  // Note that the formulation remains the same in the
794  // case that we use a weighted inner product (as
795  // in the case that we used apply_comp_scaling=true
796  // when computing the correlation matrix), see (1.28)
797  // from the lecture notes from Volkwein on POD for more
798  // details.
799  DenseVector<Real> sigma( n_snapshots );
800  DenseMatrix<Number> U( n_snapshots, n_snapshots );
801  DenseMatrix<Number> VT( n_snapshots, n_snapshots );
802  correlation_matrix.svd(sigma, U, VT );
803 
804  // We use this boolean to indicate if we will run one more iteration
805  // before exiting the loop below. We use this when computing the EIM
806  // error indicator, which requires one extra EIM iteration.
807  bool exit_on_next_iteration = false;
808 
809  // Add dominant vectors from the POD as basis functions.
810  unsigned int j = 0;
811  Real rel_err = 0.;
812 
813  // We also initialize a boolean to keep track of whether we have
814  // reached "n_snapshots" EIM basis functions, since we need to
815  // handle the EIM error indicator in a special way in this case.
816  bool j_equals_n_snapshots = false;
817  while (true)
818  {
819  bool exit_condition_satisfied = false;
820 
821  if ((j == 0) && (sigma(0) == 0.))
822  {
823  libMesh::out << "Terminating EIM POD with empty basis because first singular value is zero" << std::endl;
824  exit_condition_satisfied = true;
825  rel_err = 0.;
826  }
827  else if (j >= n_snapshots)
828  {
829  libMesh::out << "Number of basis functions equals number of training samples." << std::endl;
830  exit_condition_satisfied = true;
831  j_equals_n_snapshots = true;
832 
833  // In this case we set the rel. error to be zero since we've filled up the
834  // entire space. We cannot use the formula below for rel_err since
835  // sigma(n_snapshots) is not defined.
836  rel_err = 0.;
837  }
838  else
839  {
840  // The "energy" error in the POD approximation is determined by the first omitted
841  // singular value, i.e. sigma(j). We normalize by sigma(0), which gives the total
842  // "energy", in order to obtain a relative error.
843  rel_err = std::sqrt(sigma(j)) / std::sqrt(sigma(0));
844  }
845 
846  if (exit_on_next_iteration)
847  {
848  libMesh::out << "Extra EIM iteration for error indicator is complete, POD error norm for extra iteration: " << rel_err << std::endl;
849  break;
850  }
851 
852  libMesh::out << "Number of basis functions: " << j
853  << ", POD error norm: " << rel_err << std::endl;
854 
855  if (!exit_condition_satisfied)
856  if (j >= get_Nmax())
857  {
858  libMesh::out << "Maximum number of basis functions (" << j << ") reached." << std::endl;
859  exit_condition_satisfied = true;
860  }
861 
862  if (!exit_condition_satisfied)
863  if (rel_err < get_rel_training_tolerance())
864  {
865  libMesh::out << "Training tolerance reached." << std::endl;
866  exit_condition_satisfied = true;
867  }
868 
869  if (exit_condition_satisfied)
870  {
871  // If we're using the EIM error indicator then we need to run
872  // one extra EIM iteration since we use the extra EIM point
873  // to obtain our error indicator. If we're not using the EIM
874  // error indicator, then we just exit now.
875  bool has_parameters = (get_parameters().n_parameters() > 0);
876  if (get_rb_eim_evaluation().use_eim_error_indicator() && has_parameters)
877  {
878  exit_on_next_iteration = true;
879  libMesh::out << "EIM error indicator is active, hence we will run one extra EIM iteration before exiting"
880  << std::endl;
881  }
882  else
883  break;
884  }
885 
886  bool is_zero_bf = j_equals_n_snapshots || (rel_err == 0.);
888  {
889  // Make a "zero clone" by copying to get the same data layout, and then scaling by zero
891 
892  if (!is_zero_bf)
893  {
895 
896  for ( unsigned int i=0; i<n_snapshots; ++i )
898 
899  Real norm_v = std::sqrt(sigma(j));
900  scale_parametrized_function(v, 1./norm_v);
901  }
902 
903  libmesh_try
904  {
905  // If is_zero_bf==true then we add an "extra point" because we cannot
906  // add a usual EIM interpolation point in that case since the full EIM
907  // space is already covered. This is necessary when we want to add an
908  // extra point for error indicator purposes in the is_zero_bf==true
909  // case, for example.
910  std::unique_ptr<EimPointData> eim_point_data;
911  if (is_zero_bf)
912  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
913 
914  // If exit_on_next_iteration==true then we do not add a basis function in
915  // that case since in that case we only need to add data for the EIM error
916  // indicator.
917  bool is_linearly_dependent = enrich_eim_approximation_on_sides(v,
918  /*add_basis_function*/ !exit_on_next_iteration,
919  eim_point_data.get());
920 
921  if (is_linearly_dependent && !is_zero_bf)
922  {
923  // In this case we detected that v is actually linearly dependent and that is_zero_bf
924  // was previously not correct --- it should have been true. We typically
925  // catch this earlier (e.g. by checking rel_err) but in some cases we do not catch
926  // this until we call the enrichment method. In this situation we update is_zero_bf
927  // to true and call the enrichment again.
928  is_zero_bf = true;
929  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
930 
932  /*add_basis_function*/ !exit_on_next_iteration,
933  eim_point_data.get());
934  }
935 
936  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
937  }
938 #ifdef LIBMESH_ENABLE_EXCEPTIONS
939  catch (const std::exception & e)
940  {
941  // If we hit an exception when performing the enrichment for the error indicator, then
942  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
943  if (exit_on_next_iteration)
944  {
945  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
946  break;
947  }
948  else
949  throw;
950  }
951 #endif
952  }
953  else if (rbe.get_parametrized_function().on_mesh_nodes())
954  {
955  // Make a "zero clone" by copying to get the same data layout, and then scaling by zero
957 
958  if (!is_zero_bf)
959  {
961 
962  for ( unsigned int i=0; i<n_snapshots; ++i )
963  add_node_data_map(v, U.el(i, j), _local_node_parametrized_functions_for_training[i] );
964 
965  Real norm_v = std::sqrt(sigma(j));
966  scale_node_parametrized_function(v, 1./norm_v);
967  }
968 
969  libmesh_try
970  {
971  // If is_zero_bf==true then we add an "extra point" because we cannot
972  // add a usual EIM interpolation point in that case since the full EIM
973  // space is already covered. This is necessary when we want to add an
974  // extra point for error indicator purposes in the is_zero_bf==true
975  // case, for example.
976  std::unique_ptr<EimPointData> eim_point_data;
977  if (is_zero_bf)
978  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
979 
980  // If exit_on_next_iteration==true then we do not add a basis function in
981  // that case since in that case we only need to add data for the EIM error
982  // indicator.
983  bool is_linearly_dependent = enrich_eim_approximation_on_nodes(v,
984  /*add_basis_function*/ !exit_on_next_iteration,
985  eim_point_data.get());
986 
987  if (is_linearly_dependent && !is_zero_bf)
988  {
989  // In this case we detected that v is actually linearly dependent and that is_zero_bf
990  // was previously not correct --- it should have been true. We typically
991  // catch this earlier (e.g. by checking rel_err) but in some cases we do not catch
992  // this until we call the enrichment method. In this situation we update is_zero_bf
993  // to true and call the enrichment again.
994  is_zero_bf = true;
995  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
996 
998  /*add_basis_function*/ !exit_on_next_iteration,
999  eim_point_data.get());
1000  }
1001 
1002  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
1003  }
1004 #ifdef LIBMESH_ENABLE_EXCEPTIONS
1005  catch (const std::exception & e)
1006  {
1007  // If we hit an exception when performing the enrichment for the error indicator, then
1008  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
1009  if (exit_on_next_iteration)
1010  {
1011  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
1012  break;
1013  }
1014  else
1015  throw;
1016  }
1017 #endif
1018  }
1019  else
1020  {
1021  // Make a "zero clone" by copying to get the same data layout, and then scaling by zero
1023 
1024  if (!is_zero_bf)
1025  {
1027 
1028  for ( unsigned int i=0; i<n_snapshots; ++i )
1029  add(v, U.el(i, j), _local_parametrized_functions_for_training[i] );
1030 
1031  Real norm_v = std::sqrt(sigma(j));
1032  scale_parametrized_function(v, 1./norm_v);
1033  }
1034 
1035  libmesh_try
1036  {
1037  // If is_zero_bf==true then we add an "extra point" because we cannot
1038  // add a usual EIM interpolation point in that case since the full EIM
1039  // space is already covered. This is necessary when we want to add an
1040  // extra point for error indicator purposes in the is_zero_bf==true
1041  // case, for example.
1042  std::unique_ptr<EimPointData> eim_point_data;
1043  if (is_zero_bf)
1044  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
1045 
1046  // If exit_on_next_iteration==true then we do not add a basis function in
1047  // that case since in that case we only need to add data for the EIM error
1048  // indicator.
1049  bool is_linearly_dependent = enrich_eim_approximation_on_interiors(v,
1050  /*add_basis_function*/ !exit_on_next_iteration,
1051  eim_point_data.get());
1052 
1053  if (is_linearly_dependent && !is_zero_bf)
1054  {
1055  // In this case we detected that v is actually linearly dependent and that is_zero_bf
1056  // was previously not correct --- it should have been true. We typically
1057  // catch this earlier (e.g. by checking rel_err) but in some cases we do not catch
1058  // this until we call the enrichment method. In this situation we update is_zero_bf
1059  // to true and call the enrichment again.
1060  is_zero_bf = true;
1061  eim_point_data = std::make_unique<EimPointData>(get_random_point(v));
1062 
1064  /*add_basis_function*/ !exit_on_next_iteration,
1065  eim_point_data.get());
1066  }
1067 
1068  update_eim_matrices(/*set_error_indicator*/ exit_on_next_iteration);
1069  }
1070 #ifdef LIBMESH_ENABLE_EXCEPTIONS
1071  catch (const std::exception & e)
1072  {
1073  // If we hit an exception when performing the enrichment for the error indicator, then
1074  // we just continue and skip the error indicator. Otherwise we rethrow the exception.
1075  if (exit_on_next_iteration)
1076  {
1077  std::cout << "Exception occurred when enriching basis for error indicator hence we skip the error indicator in this case" << std::endl;
1078  break;
1079  }
1080  else
1081  throw;
1082  }
1083 #endif
1084  }
1085 
1086  if (is_zero_bf)
1087  {
1088  // In this case we exit here instead of increment j and continuing because
1089  // if we've encountered a zero EIM basis function then we must not have
1090  // any more valid data to add.
1091  std::cout << "Zero basis function encountered, hence exiting." << std::endl;
1092  break;
1093  }
1094 
1095  j++;
1096  }
1097  libMesh::out << std::endl;
1098 
1099  return rel_err;
1100 }
1101 
1103 {
1104  _rb_eim_assembly_objects.clear();
1105  for (auto i : make_range(get_rb_eim_evaluation().get_n_basis_functions()))
1107 }
1108 
1109 std::vector<std::unique_ptr<ElemAssembly>> & RBEIMConstruction::get_eim_assembly_objects()
1110 {
1111  return _rb_eim_assembly_objects;
1112 }
1113 
1115 {
1116  // Pre-request FE data for all element dimensions present in the
1117  // mesh. Note: we currently pre-request FE data for all variables
1118  // in the current system but in some cases that may be overkill, for
1119  // example if only variable 0 is used.
1120  const System & sys = c.get_system();
1121  const MeshBase & mesh = sys.get_mesh();
1122 
1123  for (unsigned int dim=1; dim<=3; ++dim)
1124  if (mesh.elem_dimensions().count(dim))
1125  for (auto var : make_range(sys.n_vars()))
1126  {
1127  auto fe = c.get_element_fe(var, dim);
1128  fe->get_JxW();
1129  fe->get_xyz();
1130  fe->get_phi();
1131 
1132  auto side_fe = c.get_side_fe(var, dim);
1133  side_fe->get_JxW();
1134  side_fe->get_xyz();
1135  side_fe->get_phi();
1136  }
1137 }
1138 
1140 {
1141  _rel_training_tolerance = new_training_tolerance;
1142 }
1143 
1145 {
1146  return _rel_training_tolerance;
1147 }
1148 
1150 {
1151  _abs_training_tolerance = new_training_tolerance;
1152 }
1153 
1155 {
1156  return _abs_training_tolerance;
1157 }
1158 
1159 unsigned int RBEIMConstruction::get_Nmax() const
1160 {
1161  return _Nmax;
1162 }
1163 
1164 void RBEIMConstruction::set_Nmax(unsigned int Nmax)
1165 {
1166  _Nmax = Nmax;
1167 }
1168 
1170 {
1173 }
1174 
1176 {
1179 }
1180 
1182 {
1184 }
1185 
1187 {
1188  LOG_SCOPE("store_eim_solutions_for_training_set()", "RBEIMConstruction");
1189 
1190  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
1191 
1192  std::vector<DenseVector<Number>> & eim_solutions = get_rb_eim_evaluation().get_eim_solutions_for_training_set();
1193  eim_solutions.clear();
1194  eim_solutions.resize(get_n_training_samples());
1195 
1196  unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
1197 
1198  for (auto i : make_range(get_n_training_samples()))
1199  {
1200  if (eim_eval.get_parametrized_function().on_mesh_sides())
1201  {
1202  const auto & local_side_pf = _local_side_parametrized_functions_for_training[i];
1203 
1204  if (RB_size > 0)
1205  {
1206  // Get the right-hand side vector for the EIM approximation
1207  // by sampling the parametrized function (stored in solution)
1208  // at the interpolation points.
1209  DenseVector<Number> EIM_rhs(RB_size);
1210  for (unsigned int j=0; j<RB_size; j++)
1211  {
1212  EIM_rhs(j) =
1214  local_side_pf,
1217  eim_eval.get_interpolation_points_comp(j),
1218  eim_eval.get_interpolation_points_qp(j));
1219  }
1220  eim_solutions[i] = eim_eval.rb_eim_solve(EIM_rhs);
1221  }
1222  }
1223  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
1224  {
1225  const auto & local_node_pf = _local_node_parametrized_functions_for_training[i];
1226 
1227  if (RB_size > 0)
1228  {
1229  // Get the right-hand side vector for the EIM approximation
1230  // by sampling the parametrized function (stored in solution)
1231  // at the interpolation points.
1232  DenseVector<Number> EIM_rhs(RB_size);
1233  for (unsigned int j=0; j<RB_size; j++)
1234  {
1235  EIM_rhs(j) =
1237  local_node_pf,
1239  eim_eval.get_interpolation_points_comp(j));
1240  }
1241  eim_solutions[i] = eim_eval.rb_eim_solve(EIM_rhs);
1242  }
1243  }
1244  else
1245  {
1246  const auto & local_pf = _local_parametrized_functions_for_training[i];
1247 
1248  if (RB_size > 0)
1249  {
1250  // Get the right-hand side vector for the EIM approximation
1251  // by sampling the parametrized function (stored in solution)
1252  // at the interpolation points.
1253  DenseVector<Number> EIM_rhs(RB_size);
1254  for (unsigned int j=0; j<RB_size; j++)
1255  {
1256  EIM_rhs(j) =
1258  local_pf,
1260  eim_eval.get_interpolation_points_comp(j),
1261  eim_eval.get_interpolation_points_qp(j));
1262  }
1263  eim_solutions[i] = eim_eval.rb_eim_solve(EIM_rhs);
1264  }
1265  }
1266  }
1267 }
1268 
1270 {
1271  libmesh_error_msg_if(training_index >= _local_parametrized_functions_for_training.size(),
1272  "Invalid index: " << training_index);
1273  return _local_parametrized_functions_for_training[training_index];
1274 }
1275 
1277 {
1278  libmesh_error_msg_if(training_index >= _local_side_parametrized_functions_for_training.size(),
1279  "Invalid index: " << training_index);
1280  return _local_side_parametrized_functions_for_training[training_index];
1281 }
1282 
1284 {
1285  libmesh_error_msg_if(training_index >= _local_node_parametrized_functions_for_training.size(),
1286  "Invalid index: " << training_index);
1287  return _local_node_parametrized_functions_for_training[training_index];
1288 }
1289 
1290 const std::unordered_map<dof_id_type, std::vector<Real> > & RBEIMConstruction::get_local_quad_point_JxW()
1291 {
1292  return _local_quad_point_JxW;
1293 }
1294 
1295 const std::map<std::pair<dof_id_type,unsigned int>, std::vector<Real> > & RBEIMConstruction::get_local_side_quad_point_JxW()
1296 {
1298 }
1299 
1301 {
1302  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1304  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1306  else
1308 }
1309 
1311 {
1313 
1314  // We need space for one extra interpolation point if we're using the
1315  // EIM error indicator.
1316  unsigned int max_matrix_size = rbe.use_eim_error_indicator() ? get_Nmax()+1 : get_Nmax();
1317  _eim_projection_matrix.resize(max_matrix_size,max_matrix_size);
1318 }
1319 
1320 std::pair<Real,unsigned int> RBEIMConstruction::compute_max_eim_error()
1321 {
1322  LOG_SCOPE("compute_max_eim_error()", "RBEIMConstruction");
1323 
1324  if (get_n_params() == 0)
1325  {
1326  // Just return 0 if we have no parameters.
1327  return std::make_pair(0.,0);
1328  }
1329 
1330  // keep track of the maximum error
1331  unsigned int max_err_index = 0;
1332  Real max_err = 0.;
1333 
1334  libmesh_error_msg_if(get_n_training_samples() != get_local_n_training_samples(),
1335  "Error: Training samples should be the same on all procs");
1336 
1337  const unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
1338 
1340  {
1341  for (auto training_index : make_range(get_n_training_samples()))
1342  {
1343  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1344  {
1345  // Make a copy of the pre-computed solution for the specified training sample
1346  // since we will modify it below to compute the best fit error.
1347  SideQpDataMap solution_copy = _local_side_parametrized_functions_for_training[training_index];
1348 
1349  // Perform an L2 projection in order to find the best approximation to
1350  // the parametrized function from the current EIM space.
1351  DenseVector<Number> best_fit_rhs(RB_size);
1352  for (unsigned int i=0; i<RB_size; i++)
1353  {
1354  best_fit_rhs(i) = side_inner_product(solution_copy,
1355  get_rb_eim_evaluation().get_side_basis_function(i),
1356  /*apply_comp_scaling*/ false);
1357  }
1358 
1359  // Now compute the best fit by an LU solve
1360  DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
1361  _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
1362 
1363  DenseVector<Number> best_fit_coeffs;
1364  RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
1365 
1366  get_rb_eim_evaluation().side_decrement_vector(solution_copy, best_fit_coeffs);
1367  Real best_fit_error = get_max_abs_value(solution_copy);
1368 
1369  if (best_fit_error > max_err)
1370  {
1371  max_err_index = training_index;
1372  max_err = best_fit_error;
1373  }
1374  }
1375  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1376  {
1377  // Make a copy of the pre-computed solution for the specified training sample
1378  // since we will modify it below to compute the best fit error.
1379  NodeDataMap solution_copy = _local_node_parametrized_functions_for_training[training_index];
1380 
1381  // Perform an L2 projection in order to find the best approximation to
1382  // the parametrized function from the current EIM space.
1383  DenseVector<Number> best_fit_rhs(RB_size);
1384  for (unsigned int i=0; i<RB_size; i++)
1385  {
1386  best_fit_rhs(i) = node_inner_product(solution_copy,
1387  get_rb_eim_evaluation().get_node_basis_function(i),
1388  /*apply_comp_scaling*/ false);
1389  }
1390 
1391  // Now compute the best fit by an LU solve
1392  DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
1393  _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
1394 
1395  DenseVector<Number> best_fit_coeffs;
1396  RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
1397 
1398  get_rb_eim_evaluation().node_decrement_vector(solution_copy, best_fit_coeffs);
1399  Real best_fit_error = get_node_max_abs_value(solution_copy);
1400 
1401  if (best_fit_error > max_err)
1402  {
1403  max_err_index = training_index;
1404  max_err = best_fit_error;
1405  }
1406  }
1407  else
1408  {
1409  // Make a copy of the pre-computed solution for the specified training sample
1410  // since we will modify it below to compute the best fit error.
1411  QpDataMap solution_copy = _local_parametrized_functions_for_training[training_index];
1412 
1413  // Perform an L2 projection in order to find the best approximation to
1414  // the parametrized function from the current EIM space.
1415  DenseVector<Number> best_fit_rhs(RB_size);
1416  for (unsigned int i=0; i<RB_size; i++)
1417  {
1418  best_fit_rhs(i) = inner_product(solution_copy,
1419  get_rb_eim_evaluation().get_basis_function(i),
1420  /*apply_comp_scaling*/ false);
1421  }
1422 
1423  // Now compute the best fit by an LU solve
1424  DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
1425  _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
1426 
1427  DenseVector<Number> best_fit_coeffs;
1428  RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
1429 
1430  get_rb_eim_evaluation().decrement_vector(solution_copy, best_fit_coeffs);
1431  Real best_fit_error = get_max_abs_value(solution_copy);
1432 
1433  if (best_fit_error > max_err)
1434  {
1435  max_err_index = training_index;
1436  max_err = best_fit_error;
1437  }
1438  }
1439  }
1440  }
1441  else if(best_fit_type_flag == EIM_BEST_FIT)
1442  {
1443  // Perform EIM solve in order to find the approximation to solution
1444  // (rb_eim_solve provides the EIM basis function coefficients used below)
1445 
1446  std::vector<RBParameters> training_parameters_copy(get_n_training_samples());
1447  for (auto training_index : make_range(get_n_training_samples()))
1448  {
1449  training_parameters_copy[training_index] = get_params_from_training_set(training_index);
1450  }
1451 
1452  get_rb_eim_evaluation().rb_eim_solves(training_parameters_copy, RB_size);
1453  const std::vector<DenseVector<Number>> & rb_eim_solutions = get_rb_eim_evaluation().get_rb_eim_solutions();
1454 
1455  for (auto training_index : make_range(get_n_training_samples()))
1456  {
1457  const DenseVector<Number> & best_fit_coeffs = rb_eim_solutions[training_index];
1458 
1459  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1460  {
1461  SideQpDataMap solution_copy = _local_side_parametrized_functions_for_training[training_index];
1462  get_rb_eim_evaluation().side_decrement_vector(solution_copy, best_fit_coeffs);
1463  Real best_fit_error = get_max_abs_value(solution_copy);
1464 
1465  if (best_fit_error > max_err)
1466  {
1467  max_err_index = training_index;
1468  max_err = best_fit_error;
1469  }
1470  }
1471  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
1472  {
1473  NodeDataMap solution_copy = _local_node_parametrized_functions_for_training[training_index];
1474  get_rb_eim_evaluation().node_decrement_vector(solution_copy, best_fit_coeffs);
1475  Real best_fit_error = get_node_max_abs_value(solution_copy);
1476 
1477  if (best_fit_error > max_err)
1478  {
1479  max_err_index = training_index;
1480  max_err = best_fit_error;
1481  }
1482  }
1483  else
1484  {
1485  QpDataMap solution_copy = _local_parametrized_functions_for_training[training_index];
1486  get_rb_eim_evaluation().decrement_vector(solution_copy, best_fit_coeffs);
1487  Real best_fit_error = get_max_abs_value(solution_copy);
1488 
1489  if (best_fit_error > max_err)
1490  {
1491  max_err_index = training_index;
1492  max_err = best_fit_error;
1493  }
1494  }
1495  }
1496  }
1497  else
1498  {
1499  libmesh_error_msg("EIM best fit type not recognized");
1500  }
1501 
1502  return std::make_pair(max_err,max_err_index);
1503 }
1504 
1506 {
1507  LOG_SCOPE("initialize_parametrized_functions_in_training_set()", "RBEIMConstruction");
1508 
1509  libmesh_error_msg_if(!serial_training_set,
1510  "Error: We must have serial_training_set==true in "
1511  "RBEIMConstruction::initialize_parametrized_functions_in_training_set");
1512 
1513  libMesh::out << "Initializing parametrized functions in training set..." << std::endl;
1514 
1515  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
1516 
1519 
1520  // Store the locations of all quadrature points
1522 
1523  // Keep track of the largest value in our parametrized functions
1524  // in the training set. We can use this value for normalization
1525  // purposes, for example.
1527 
1528  unsigned int n_comps = eim_eval.get_parametrized_function().get_n_components();
1529 
1530  // Keep track of the maximum value per component. This will allow
1531  // us to scale the components to all have a similar magnitude,
1532  // which is helpful during the error assessment for the basis
1533  // enrichment to ensure that components with smaller magnitude
1534  // are not ignored.
1535  std::vector<Real> max_abs_value_per_component_in_training_set(n_comps);
1536 
1537  if (eim_eval.get_parametrized_function().on_mesh_sides())
1538  {
1540  for (auto i : make_range(get_n_training_samples()))
1541  {
1542  libMesh::out << "Initializing parametrized function for training sample "
1543  << (i+1) << " of " << get_n_training_samples() << std::endl;
1544 
1546 
1553  *this);
1554 
1555  for (const auto & [elem_side_pair, xyz_vector] : _local_side_quad_point_locations)
1556  {
1557  std::vector<std::vector<Number>> comps_and_qps(n_comps);
1558  for (unsigned int comp=0; comp<n_comps; comp++)
1559  {
1560  comps_and_qps[comp].resize(xyz_vector.size());
1561  for (unsigned int qp : index_range(xyz_vector))
1562  {
1563  Number value =
1565  elem_side_pair.first,
1566  elem_side_pair.second,
1567  qp);
1568  comps_and_qps[comp][qp] = value;
1569 
1570  Real abs_value = std::abs(value);
1571  if (abs_value > _max_abs_value_in_training_set)
1572  {
1573  _max_abs_value_in_training_set = abs_value;
1575  }
1576 
1577  if (abs_value > max_abs_value_per_component_in_training_set[comp])
1578  max_abs_value_per_component_in_training_set[comp] = abs_value;
1579  }
1580  }
1581 
1582  _local_side_parametrized_functions_for_training[i][elem_side_pair] = comps_and_qps;
1583  }
1584  }
1585 
1586  libMesh::out << "Parametrized functions in training set initialized" << std::endl;
1587 
1588  unsigned int max_id = 0;
1591  libMesh::out << "Maximum absolute value in the training set: "
1592  << _max_abs_value_in_training_set << std::endl << std::endl;
1593 
1594  // Calculate the maximum value for each component in the training set
1595  // across all components
1596  comm().max(max_abs_value_per_component_in_training_set);
1597 
1598  // We store the maximum value across all components divided by the maximum value for this component
1599  // so that when we scale using these factors all components should have a magnitude on the same
1600  // order as the maximum component.
1601  _component_scaling_in_training_set.resize(n_comps);
1602  for (unsigned int i : make_range(n_comps))
1603  {
1604  if ((eim_eval.scale_components_in_enrichment().count(i) == 0) ||
1605  max_abs_value_per_component_in_training_set[i] == 0.)
1607  else
1608  _component_scaling_in_training_set[i] = _max_abs_value_in_training_set / max_abs_value_per_component_in_training_set[i];
1609  }
1610  }
1611  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
1612  {
1614  for (auto i : make_range(get_n_training_samples()))
1615  {
1616  libMesh::out << "Initializing parametrized function for training sample "
1617  << (i+1) << " of " << get_n_training_samples() << std::endl;
1618 
1620 
1624  *this);
1625 
1626  for (const auto & pr : _local_node_locations)
1627  {
1628  const auto & node_id = pr.first;
1629 
1630  std::vector<Number> comps(n_comps);
1631  for (unsigned int comp=0; comp<n_comps; comp++)
1632  {
1633  Number value =
1635  node_id);
1636  comps[comp] = value;
1637 
1638  Real abs_value = std::abs(value);
1639  if (abs_value > _max_abs_value_in_training_set)
1640  {
1641  _max_abs_value_in_training_set = abs_value;
1643  }
1644 
1645  if (abs_value > max_abs_value_per_component_in_training_set[comp])
1646  max_abs_value_per_component_in_training_set[comp] = abs_value;
1647  }
1648 
1650  }
1651  }
1652 
1653  libMesh::out << "Parametrized functions in training set initialized" << std::endl;
1654 
1655  unsigned int max_id = 0;
1658  libMesh::out << "Maximum absolute value in the training set: "
1659  << _max_abs_value_in_training_set << std::endl << std::endl;
1660 
1661  // Calculate the maximum value for each component in the training set
1662  // across all components
1663  comm().max(max_abs_value_per_component_in_training_set);
1664 
1665  // We store the maximum value across all components divided by the maximum value for this component
1666  // so that when we scale using these factors all components should have a magnitude on the same
1667  // order as the maximum component.
1668  _component_scaling_in_training_set.resize(n_comps);
1669  for (unsigned int i : make_range(n_comps))
1670  {
1671  if ((eim_eval.scale_components_in_enrichment().count(i) == 0) ||
1672  max_abs_value_per_component_in_training_set[i] == 0.)
1674  else
1675  _component_scaling_in_training_set[i] = _max_abs_value_in_training_set / max_abs_value_per_component_in_training_set[i];
1676  }
1677  }
1678  else
1679  {
1681  for (auto i : make_range(get_n_training_samples()))
1682  {
1683  libMesh::out << "Initializing parametrized function for training sample "
1684  << (i+1) << " of " << get_n_training_samples() << std::endl;
1685 
1687 
1692  *this);
1693 
1694  for (const auto & [elem_id, xyz_vector] : _local_quad_point_locations)
1695  {
1696  std::vector<std::vector<Number>> comps_and_qps(n_comps);
1697  for (unsigned int comp=0; comp<n_comps; comp++)
1698  {
1699  comps_and_qps[comp].resize(xyz_vector.size());
1700  for (unsigned int qp : index_range(xyz_vector))
1701  {
1702  Number value =
1703  eim_eval.get_parametrized_function().lookup_preevaluated_value_on_mesh(comp, elem_id, qp);
1704  comps_and_qps[comp][qp] = value;
1705 
1706  Real abs_value = std::abs(value);
1707  if (abs_value > _max_abs_value_in_training_set)
1708  {
1709  _max_abs_value_in_training_set = abs_value;
1711  }
1712 
1713  if (abs_value > max_abs_value_per_component_in_training_set[comp])
1714  max_abs_value_per_component_in_training_set[comp] = abs_value;
1715  }
1716  }
1717 
1718  _local_parametrized_functions_for_training[i][elem_id] = comps_and_qps;
1719  }
1720  }
1721 
1722  libMesh::out << "Parametrized functions in training set initialized" << std::endl;
1723 
1724  unsigned int max_id = 0;
1727  libMesh::out << "Maximum absolute value in the training set: "
1728  << _max_abs_value_in_training_set << std::endl << std::endl;
1729 
1730  // Calculate the maximum value for each component in the training set
1731  // across all components
1732  comm().max(max_abs_value_per_component_in_training_set);
1733 
1734  // We store the maximum value across all components divided by the maximum value for this component
1735  // so that when we scale using these factors all components should have a magnitude on the same
1736  // order as the maximum component.
1737  _component_scaling_in_training_set.resize(n_comps);
1738  for (unsigned int i : make_range(n_comps))
1739  {
1740  if ((eim_eval.scale_components_in_enrichment().count(i) == 0) ||
1741  max_abs_value_per_component_in_training_set[i] == 0.)
1743  else
1744  _component_scaling_in_training_set[i] = _max_abs_value_in_training_set / max_abs_value_per_component_in_training_set[i];
1745  }
1746  }
1747  // This function does nothing if rb_property_map from RBParametrizedFunction
1748  // is empty which would result in an empty rb_property_map in VectorizedEvalInput
1749  // stored in RBEIMEvaluation.
1750  eim_eval.initialize_rb_property_map();
1751 }
1752 
1754 {
1755  LOG_SCOPE("initialize_qp_data()", "RBEIMConstruction");
1756 
1757  if (!get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
1758  {
1759  libMesh::out << "Initializing quadrature point locations" << std::endl;
1760  }
1761  else
1762  {
1763  libMesh::out << "Initializing quadrature point and perturbation locations" << std::endl;
1764  }
1765 
1766  // Compute truth representation via L2 projection
1767  const MeshBase & mesh = this->get_mesh();
1768 
1769  FEMContext context(*this);
1770  init_context(context);
1771 
1772  if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_sides())
1773  {
1774  const std::set<boundary_id_type> & parametrized_function_boundary_ids =
1776  libmesh_error_msg_if (parametrized_function_boundary_ids.empty(),
1777  "Need to have non-empty boundary IDs to initialize side data");
1778 
1784 
1786 
1787  // BoundaryInfo and related data structures
1788  const auto & binfo = mesh.get_boundary_info();
1789  std::vector<boundary_id_type> side_boundary_ids;
1790 
1791  for (const auto & elem : mesh.active_local_element_ptr_range())
1792  {
1793  dof_id_type elem_id = elem->id();
1794 
1795  context.pre_fe_reinit(*this, elem);
1796 
1797  // elem_fe is used for shellface data
1798  auto elem_fe = context.get_element_fe(/*var=*/0, elem->dim());
1799  const std::vector<Real> & JxW = elem_fe->get_JxW();
1800  const std::vector<Point> & xyz = elem_fe->get_xyz();
1801 
1802  // side_fe is used for element side data
1803  auto side_fe = context.get_side_fe(/*var=*/0, elem->dim());
1804  const std::vector<Real> & JxW_side = side_fe->get_JxW();
1805  const std::vector< Point > & xyz_side = side_fe->get_xyz();
1806 
1807  for (context.side = 0;
1808  context.side != context.get_elem().n_sides();
1809  ++context.side)
1810  {
1811  // skip non-boundary elements
1812  if(!context.get_elem().neighbor_ptr(context.side))
1813  {
1814  binfo.boundary_ids(elem, context.side, side_boundary_ids);
1815 
1816  bool has_side_boundary_id = false;
1817  boundary_id_type matching_boundary_id = BoundaryInfo::invalid_id;
1818  for (boundary_id_type side_boundary_id : side_boundary_ids)
1819  if(parametrized_function_boundary_ids.count(side_boundary_id))
1820  {
1821  has_side_boundary_id = true;
1822  matching_boundary_id = side_boundary_id;
1823  break;
1824  }
1825 
1826  if(has_side_boundary_id)
1827  {
1828  context.get_side_fe(/*var=*/0, elem->dim())->reinit(elem, context.side);
1829 
1830  auto elem_side_pair = std::make_pair(elem_id, context.side);
1831 
1832  _local_side_quad_point_locations[elem_side_pair] = xyz_side;
1833  _local_side_quad_point_JxW[elem_side_pair] = JxW_side;
1834  _local_side_quad_point_subdomain_ids[elem_side_pair] = elem->subdomain_id();
1835  _local_side_quad_point_boundary_ids[elem_side_pair] = matching_boundary_id;
1836 
1837  // This is a standard side (not a shellface) so set side type to 0
1838  _local_side_quad_point_side_types[elem_side_pair] = 0;
1839 
1840  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
1841  {
1843 
1844  std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
1845 
1846  for (const Point & xyz_qp : xyz_side)
1847  {
1848  std::vector<Point> xyz_perturb_vec;
1849  if (elem->dim() == 3)
1850  {
1851  // In this case we have a 3D element, and hence the side is 2D.
1852  //
1853  // We use the following approach to perturb xyz:
1854  // 1) inverse map xyz to the reference element
1855  // 2) perturb on the reference element in the (xi,eta) "directions"
1856  // 3) map the perturbed points back to the physical element
1857  // This approach is necessary to ensure that the perturbed points
1858  // are still in the element's side.
1859 
1860  std::unique_ptr<const Elem> elem_side;
1861  elem->build_side_ptr(elem_side, context.side);
1862 
1863  Point xi_eta =
1864  FEMap::inverse_map(elem_side->dim(),
1865  elem_side.get(),
1866  xyz_qp,
1867  /*Newton iteration tolerance*/ TOLERANCE,
1868  /*secure*/ true);
1869 
1870  // Inverse map should map back to a 2D reference domain
1871  libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
1872 
1873  Point xi_eta_perturb = xi_eta;
1874 
1875  xi_eta_perturb(0) += fd_delta;
1876  Point xyz_perturb_0 =
1877  FEMap::map(elem_side->dim(),
1878  elem_side.get(),
1879  xi_eta_perturb);
1880  xi_eta_perturb(0) -= fd_delta;
1881 
1882  xi_eta_perturb(1) += fd_delta;
1883  Point xyz_perturb_1 =
1884  FEMap::map(elem_side->dim(),
1885  elem_side.get(),
1886  xi_eta_perturb);
1887  xi_eta_perturb(1) -= fd_delta;
1888 
1889  // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
1890  // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
1891  // required in order to compute finite differences correctly.
1892  Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
1893  Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
1894 
1895  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
1896  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
1897  }
1898  else
1899  {
1900  // We current do nothing for sides of dim=2 or dim=1 elements
1901  // since we have no need for this capability so far.
1902  // Support for these cases could be added if it is needed.
1903  }
1904 
1905  xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
1906  }
1907 
1908  _local_side_quad_point_locations_perturbations[elem_side_pair] = xyz_perturb_vec_at_qps;
1909  }
1910  }
1911  }
1912  }
1913 
1914  // In the case of 2D elements, we also check the shellfaces
1915  if (elem->dim() == 2)
1916  for (unsigned int shellface_index=0; shellface_index<2; shellface_index++)
1917  {
1918  binfo.shellface_boundary_ids(elem, shellface_index, side_boundary_ids);
1919 
1920  bool has_side_boundary_id = false;
1921  boundary_id_type matching_boundary_id = BoundaryInfo::invalid_id;
1922  for (boundary_id_type side_boundary_id : side_boundary_ids)
1923  if(parametrized_function_boundary_ids.count(side_boundary_id))
1924  {
1925  has_side_boundary_id = true;
1926  matching_boundary_id = side_boundary_id;
1927  break;
1928  }
1929 
1930  if(has_side_boundary_id)
1931  {
1932  context.elem_fe_reinit();
1933 
1934  // We use shellface_index as the side_index since shellface boundary conditions
1935  // are stored separately from side boundary conditions in BoundaryInfo.
1936  auto elem_side_pair = std::make_pair(elem_id, shellface_index);
1937 
1938  _local_side_quad_point_locations[elem_side_pair] = xyz;
1939  _local_side_quad_point_JxW[elem_side_pair] = JxW;
1940  _local_side_quad_point_subdomain_ids[elem_side_pair] = elem->subdomain_id();
1941  _local_side_quad_point_boundary_ids[elem_side_pair] = matching_boundary_id;
1942 
1943  // This is a shellface (not a standard side) so set side type to 1
1944  _local_side_quad_point_side_types[elem_side_pair] = 1;
1945 
1946  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
1947  {
1949 
1950  std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
1951 
1952  for (const Point & xyz_qp : xyz)
1953  {
1954  std::vector<Point> xyz_perturb_vec;
1955  // Here we follow the same approach as above for getting xyz_perturb_vec,
1956  // except that we are using the element itself instead of its side.
1957  {
1958  Point xi_eta =
1959  FEMap::inverse_map(elem->dim(),
1960  elem,
1961  xyz_qp,
1962  /*Newton iteration tolerance*/ TOLERANCE,
1963  /*secure*/ true);
1964 
1965  // Inverse map should map back to a 2D reference domain
1966  libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
1967 
1968  Point xi_eta_perturb = xi_eta;
1969 
1970  xi_eta_perturb(0) += fd_delta;
1971  Point xyz_perturb_0 =
1972  FEMap::map(elem->dim(),
1973  elem,
1974  xi_eta_perturb);
1975  xi_eta_perturb(0) -= fd_delta;
1976 
1977  xi_eta_perturb(1) += fd_delta;
1978  Point xyz_perturb_1 =
1979  FEMap::map(elem->dim(),
1980  elem,
1981  xi_eta_perturb);
1982  xi_eta_perturb(1) -= fd_delta;
1983 
1984  // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
1985  // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
1986  // required in order to compute finite differences correctly.
1987  Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
1988  Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
1989 
1990  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
1991  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
1992  }
1993 
1994  xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
1995  }
1996 
1997  _local_side_quad_point_locations_perturbations[elem_side_pair] = xyz_perturb_vec_at_qps;
1998  }
1999  }
2000  }
2001  }
2002  }
2003  else if (get_rb_eim_evaluation().get_parametrized_function().on_mesh_nodes())
2004  {
2005  const std::set<boundary_id_type> & parametrized_function_boundary_ids =
2007  libmesh_error_msg_if (parametrized_function_boundary_ids.empty(),
2008  "Need to have non-empty boundary IDs to initialize node data");
2009 
2010  _local_node_locations.clear();
2011  _local_node_boundary_ids.clear();
2012 
2013  const auto & binfo = mesh.get_boundary_info();
2014 
2015  // Make a set with all the nodes that have nodesets. Use
2016  // a set so that we don't have any duplicate entries. We
2017  // deal with duplicate entries below by getting all boundary
2018  // IDs on each node.
2019  std::set<dof_id_type> nodes_with_nodesets;
2020  for (const auto & t : binfo.build_node_list())
2021  nodes_with_nodesets.insert(std::get<0>(t));
2022 
2023  // To be filled in by BoundaryInfo calls in loop below
2024  std::vector<boundary_id_type> node_boundary_ids;
2025 
2026  for (dof_id_type node_id : nodes_with_nodesets)
2027  {
2028  const Node * node = mesh.node_ptr(node_id);
2029 
2030  if (node->processor_id() != mesh.comm().rank())
2031  continue;
2032 
2033  binfo.boundary_ids(node, node_boundary_ids);
2034 
2035  bool has_node_boundary_id = false;
2036  boundary_id_type matching_boundary_id = BoundaryInfo::invalid_id;
2037  for (boundary_id_type node_boundary_id : node_boundary_ids)
2038  if(parametrized_function_boundary_ids.count(node_boundary_id))
2039  {
2040  has_node_boundary_id = true;
2041  matching_boundary_id = node_boundary_id;
2042  break;
2043  }
2044 
2045  if(has_node_boundary_id)
2046  {
2047  _local_node_locations[node_id] = *node;
2048  _local_node_boundary_ids[node_id] = matching_boundary_id;
2049  }
2050  }
2051  }
2052  else
2053  {
2056  _local_quad_point_JxW.clear();
2057 
2059 
2060  for (const auto & elem : mesh.active_local_element_ptr_range())
2061  {
2062  auto elem_fe = context.get_element_fe(/*var=*/0, elem->dim());
2063  const std::vector<Real> & JxW = elem_fe->get_JxW();
2064  const std::vector<Point> & xyz = elem_fe->get_xyz();
2065 
2066  dof_id_type elem_id = elem->id();
2067 
2068  context.pre_fe_reinit(*this, elem);
2069  context.elem_fe_reinit();
2070 
2071  _local_quad_point_locations[elem_id] = xyz;
2072  _local_quad_point_JxW[elem_id] = JxW;
2073  _local_quad_point_subdomain_ids[elem_id] = elem->subdomain_id();
2074 
2075  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
2076  {
2078 
2079  std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
2080 
2081  for (const Point & xyz_qp : xyz)
2082  {
2083  std::vector<Point> xyz_perturb_vec;
2084  if (elem->dim() == 3)
2085  {
2086  Point xyz_perturb = xyz_qp;
2087 
2088  xyz_perturb(0) += fd_delta;
2089  xyz_perturb_vec.emplace_back(xyz_perturb);
2090  xyz_perturb(0) -= fd_delta;
2091 
2092  xyz_perturb(1) += fd_delta;
2093  xyz_perturb_vec.emplace_back(xyz_perturb);
2094  xyz_perturb(1) -= fd_delta;
2095 
2096  xyz_perturb(2) += fd_delta;
2097  xyz_perturb_vec.emplace_back(xyz_perturb);
2098  xyz_perturb(2) -= fd_delta;
2099  }
2100  else if (elem->dim() == 2)
2101  {
2102  // In this case we assume that we have a 2D element
2103  // embedded in 3D space. In this case we have to use
2104  // the following approach to perturb xyz:
2105  // 1) inverse map xyz to the reference element
2106  // 2) perturb on the reference element in the (xi,eta) "directions"
2107  // 3) map the perturbed points back to the physical element
2108  // This approach is necessary to ensure that the perturbed points
2109  // are still in the element.
2110 
2111  Point xi_eta =
2112  FEMap::inverse_map(elem->dim(),
2113  elem,
2114  xyz_qp,
2115  /*Newton iteration tolerance*/ TOLERANCE,
2116  /*secure*/ true);
2117 
2118  // Inverse map should map back to a 2D reference domain
2119  libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
2120 
2121  Point xi_eta_perturb = xi_eta;
2122 
2123  xi_eta_perturb(0) += fd_delta;
2124  Point xyz_perturb_0 =
2125  FEMap::map(elem->dim(),
2126  elem,
2127  xi_eta_perturb);
2128  xi_eta_perturb(0) -= fd_delta;
2129 
2130  xi_eta_perturb(1) += fd_delta;
2131  Point xyz_perturb_1 =
2132  FEMap::map(elem->dim(),
2133  elem,
2134  xi_eta_perturb);
2135  xi_eta_perturb(1) -= fd_delta;
2136 
2137  // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
2138  // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
2139  // required in order to compute finite differences correctly.
2140  Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
2141  Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
2142 
2143  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
2144  xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
2145  }
2146  else
2147  {
2148  // We current do nothing in the dim=1 case since
2149  // we have no need for this capability so far.
2150  // Support for this case could be added if it is
2151  // needed.
2152  }
2153 
2154  xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
2155  }
2156 
2157  _local_quad_point_locations_perturbations[elem_id] = xyz_perturb_vec_at_qps;
2158  }
2159  }
2160  }
2161 }
2162 
2163 Number
2164 RBEIMConstruction::inner_product(const QpDataMap & v, const QpDataMap & w, bool apply_comp_scaling)
2165 {
2166  LOG_SCOPE("inner_product()", "RBEIMConstruction");
2167 
2168  Number val = 0.;
2169 
2170  for (const auto & [elem_id, v_comp_and_qp] : v)
2171  {
2172  const auto & w_comp_and_qp = libmesh_map_find(w, elem_id);
2173  const auto & JxW = libmesh_map_find(_local_quad_point_JxW, elem_id);
2174 
2175  for (const auto & comp : index_range(v_comp_and_qp))
2176  {
2177  const std::vector<Number> & v_qp = v_comp_and_qp[comp];
2178  const std::vector<Number> & w_qp = w_comp_and_qp[comp];
2179 
2180  Real comp_scaling = 1.;
2181  if (apply_comp_scaling)
2182  {
2183  // We square the component scaling here because it occurs twice in
2184  // the inner product calculation below.
2185  comp_scaling = std::pow(_component_scaling_in_training_set[comp], 2.);
2186  }
2187 
2188  for (unsigned int qp : index_range(JxW))
2189  val += JxW[qp] * comp_scaling * v_qp[qp] * libmesh_conj(w_qp[qp]);
2190  }
2191  }
2192 
2193  comm().sum(val);
2194  return val;
2195 }
2196 
2197 Number
2198 RBEIMConstruction::side_inner_product(const SideQpDataMap & v, const SideQpDataMap & w, bool apply_comp_scaling)
2199 {
2200  LOG_SCOPE("side_inner_product()", "RBEIMConstruction");
2201 
2202  Number val = 0.;
2203 
2204  for (const auto & [elem_and_side, v_comp_and_qp] : v)
2205  {
2206  const auto & w_comp_and_qp = libmesh_map_find(w, elem_and_side);
2207  const auto & JxW = libmesh_map_find(_local_side_quad_point_JxW, elem_and_side);
2208 
2209  for (const auto & comp : index_range(v_comp_and_qp))
2210  {
2211  const std::vector<Number> & v_qp = v_comp_and_qp[comp];
2212  const std::vector<Number> & w_qp = w_comp_and_qp[comp];
2213 
2214  Real comp_scaling = 1.;
2215  if (apply_comp_scaling)
2216  {
2217  // We square the component scaling here because it occurs twice in
2218  // the inner product calculation below.
2219  comp_scaling = std::pow(_component_scaling_in_training_set[comp], 2.);
2220  }
2221 
2222  for (unsigned int qp : index_range(JxW))
2223  val += JxW[qp] * comp_scaling * v_qp[qp] * libmesh_conj(w_qp[qp]);
2224  }
2225  }
2226 
2227  comm().sum(val);
2228  return val;
2229 }
2230 
2231 Number
2232 RBEIMConstruction::node_inner_product(const NodeDataMap & v, const NodeDataMap & w, bool apply_comp_scaling)
2233 {
2234  LOG_SCOPE("node_inner_product()", "RBEIMConstruction");
2235 
2236  Number val = 0.;
2237 
2238  for (const auto & [node_id, v_comps] : v)
2239  {
2240  const auto & w_comps = libmesh_map_find(w, node_id);
2241 
2242  for (const auto & comp : index_range(v_comps))
2243  {
2244  // There is no quadrature rule on nodes, so we just multiply the values directly.
2245  // Hence we effectively work with the Euclidean inner product in this case.
2246 
2247  Real comp_scaling = 1.;
2248  if (apply_comp_scaling)
2249  {
2250  // We square the component scaling here because it occurs twice in
2251  // the inner product calculation below.
2252  comp_scaling = std::pow(_component_scaling_in_training_set[comp], 2.);
2253  }
2254 
2255  val += comp_scaling * v_comps[comp] * libmesh_conj(w_comps[comp]);
2256  }
2257  }
2258 
2259  comm().sum(val);
2260  return val;
2261 }
2262 
2264 {
2265  Real max_value = 0.;
2266 
2267  for (const auto & pr : v)
2268  {
2269  const auto & values = pr.second;
2270  for (const auto & comp : index_range(values))
2271  {
2272  const auto & value = values[comp];
2273 
2274  Real comp_scaling = 1.;
2275  if (get_rb_eim_evaluation().scale_components_in_enrichment().count(comp))
2276  {
2277  // Make sure that _component_scaling_in_training_set is initialized
2278  libmesh_error_msg_if(comp >= _component_scaling_in_training_set.size(),
2279  "Invalid vector index");
2280  comp_scaling = _component_scaling_in_training_set[comp];
2281  }
2282 
2283  max_value = std::max(max_value, std::abs(value * comp_scaling));
2284  }
2285  }
2286 
2287  comm().max(max_value);
2288  return max_value;
2289 }
2290 
2291 void RBEIMConstruction::enrich_eim_approximation(unsigned int training_index,
2292  bool add_basis_function,
2293  EimPointData * eim_point_data)
2294 {
2295  LOG_SCOPE("enrich_eim_approximation()", "RBEIMConstruction");
2296 
2297  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2298 
2299  set_params_from_training_set(training_index);
2300 
2301  if (eim_eval.get_parametrized_function().on_mesh_sides())
2303  add_basis_function,
2304  eim_point_data);
2305  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
2307  add_basis_function,
2308  eim_point_data);
2309  else
2310  {
2312  add_basis_function,
2313  eim_point_data);
2314  }
2315 }
2316 
2318  bool add_basis_function,
2319  EimPointData * eim_point_data)
2320 {
2321  // Make a copy of the input parametrized function, since we will modify this below
2322  // to give us a new basis function.
2323  SideQpDataMap local_pf = side_pf;
2324 
2325  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2326 
2327  // If we have at least one basis function, then we need to use
2328  // rb_eim_solve() to find the EIM interpolation error. Otherwise,
2329  // just use solution as is.
2330  if (!eim_point_data && (eim_eval.get_n_basis_functions() > 0))
2331  {
2332  // Get the right-hand side vector for the EIM approximation
2333  // by sampling the parametrized function (stored in solution)
2334  // at the interpolation points.
2335  unsigned int RB_size = eim_eval.get_n_basis_functions();
2336  DenseVector<Number> EIM_rhs(RB_size);
2337  for (unsigned int i=0; i<RB_size; i++)
2338  {
2339  EIM_rhs(i) =
2341  local_pf,
2344  eim_eval.get_interpolation_points_comp(i),
2345  eim_eval.get_interpolation_points_qp(i));
2346  }
2347 
2348  eim_eval.set_parameters( get_parameters() );
2349  DenseVector<Number> rb_eim_solution = eim_eval.rb_eim_solve(EIM_rhs);
2350 
2351  // Load the "EIM residual" into solution by subtracting
2352  // the EIM approximation
2353  eim_eval.side_decrement_vector(local_pf, rb_eim_solution);
2354  }
2355 
2356  // Find the quadrature point at which local_pf (which now stores
2357  // the "EIM residual") has maximum absolute value
2358  Number optimal_value = 0.;
2359  Point optimal_point;
2360  unsigned int optimal_comp = 0;
2361  dof_id_type optimal_elem_id = DofObject::invalid_id;
2362  unsigned int optimal_side_index = 0;
2363  subdomain_id_type optimal_subdomain_id = 0;
2364  boundary_id_type optimal_boundary_id = 0;
2365  unsigned int optimal_qp = 0;
2366  std::vector<Point> optimal_point_perturbs;
2367  std::vector<Real> optimal_point_phi_i_qp;
2368 
2369  // Initialize largest_abs_value to be negative so that it definitely gets updated.
2370  Real largest_abs_value = -1.;
2371 
2372  // In order to compute phi_i_qp, we initialize a FEMContext
2373  FEMContext con(*this);
2374  init_context(con);
2375 
2376  for (const auto & [elem_and_side, comp_and_qp] : local_pf)
2377  {
2378  dof_id_type elem_id = elem_and_side.first;
2379  unsigned int side_index = elem_and_side.second;
2380 
2381  const Elem & elem_ref = get_mesh().elem_ref(elem_id);
2382  con.pre_fe_reinit(*this, &elem_ref);
2383 
2384  unsigned int side_type = libmesh_map_find(_local_side_quad_point_side_types, elem_and_side);
2385 
2386  std::vector<std::vector<Real>> phi;
2387  // side_type == 0 --> standard side
2388  // side_type == 1 --> shellface
2389  if (side_type == 0)
2390  {
2391  // TODO: We only want the "dofs on side" entries
2392  // from phi_side. Could do this by initing an FE object
2393  // on the side itself, rather than using get_side_fe().
2394  auto side_fe = con.get_side_fe(/*var=*/ 0);
2395  side_fe->reinit(&elem_ref, side_index);
2396 
2397  phi = side_fe->get_phi();
2398  }
2399  else if (side_type == 1)
2400  {
2401  con.elem_fe_reinit();
2402 
2403  auto elem_fe = con.get_element_fe(/*var=*/0, elem_ref.dim());
2404  phi = elem_fe->get_phi();
2405  }
2406  else
2407  libmesh_error_msg ("Unrecognized side_type: " << side_type);
2408 
2409  for (const auto & comp : index_range(comp_and_qp))
2410  {
2411  const std::vector<Number> & qp_values = comp_and_qp[comp];
2412 
2413  for (auto qp : index_range(qp_values))
2414  {
2415  Number value = qp_values[qp];
2416  Real abs_value = std::abs(value);
2417 
2418  bool update_optimal_point = false;
2419  if (!eim_point_data)
2420  update_optimal_point = (abs_value > largest_abs_value);
2421  else
2422  update_optimal_point = (elem_id == eim_point_data->elem_id) &&
2423  (side_index == eim_point_data->side_index) &&
2424  (comp == eim_point_data->comp_index) &&
2425  (qp == eim_point_data->qp_index);
2426 
2427  if (update_optimal_point)
2428  {
2429  largest_abs_value = abs_value;
2430  optimal_value = value;
2431  optimal_comp = comp;
2432  optimal_elem_id = elem_id;
2433  optimal_side_index = side_index;
2434  optimal_qp = qp;
2435 
2436  optimal_point_phi_i_qp.resize(phi.size());
2437  for (auto i : index_range(phi))
2438  optimal_point_phi_i_qp[i] = phi[i][qp];
2439 
2440  const auto & point_list =
2441  libmesh_map_find(_local_side_quad_point_locations, elem_and_side);
2442 
2443  libmesh_error_msg_if(qp >= point_list.size(), "Error: Invalid qp");
2444 
2445  optimal_point = point_list[qp];
2446 
2447  optimal_subdomain_id = libmesh_map_find(_local_side_quad_point_subdomain_ids, elem_and_side);
2448  optimal_boundary_id = libmesh_map_find(_local_side_quad_point_boundary_ids, elem_and_side);
2449 
2450  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
2451  {
2452  const auto & perturb_list =
2453  libmesh_map_find(_local_side_quad_point_locations_perturbations, elem_and_side);
2454 
2455  libmesh_error_msg_if(qp >= perturb_list.size(), "Error: Invalid qp");
2456 
2457  optimal_point_perturbs = perturb_list[qp];
2458  }
2459  }
2460  }
2461  }
2462  }
2463 
2464  // Find out which processor has the largest of the abs values
2465  // and broadcast from that processor.
2466  unsigned int proc_ID_index;
2467  this->comm().maxloc(largest_abs_value, proc_ID_index);
2468 
2469  this->comm().broadcast(optimal_value, proc_ID_index);
2470  this->comm().broadcast(optimal_point, proc_ID_index);
2471  this->comm().broadcast(optimal_comp, proc_ID_index);
2472  this->comm().broadcast(optimal_elem_id, proc_ID_index);
2473  this->comm().broadcast(optimal_side_index, proc_ID_index);
2474  this->comm().broadcast(optimal_subdomain_id, proc_ID_index);
2475  this->comm().broadcast(optimal_boundary_id, proc_ID_index);
2476  this->comm().broadcast(optimal_qp, proc_ID_index);
2477  this->comm().broadcast(optimal_point_perturbs, proc_ID_index);
2478  this->comm().broadcast(optimal_point_phi_i_qp, proc_ID_index);
2479 
2480  libmesh_error_msg_if(optimal_elem_id == DofObject::invalid_id, "Error: Invalid element ID");
2481 
2482  if (add_basis_function)
2483  {
2484  if (optimal_value == 0.)
2485  {
2486  libMesh::out << "Encountered linearly dependent data in EIM enrichment, hence skip adding new basis function" << std::endl;
2487  return true;
2488  }
2489 
2490  // Scale local_pf so that its largest value is 1.0
2491  scale_parametrized_function(local_pf, 1./optimal_value);
2492 
2493  // Add local_pf as the new basis function and store data
2494  // associated with the interpolation point.
2495  eim_eval.add_side_basis_function(local_pf);
2496  }
2497 
2498  eim_eval.add_side_interpolation_data(optimal_point,
2499  optimal_comp,
2500  optimal_elem_id,
2501  optimal_side_index,
2502  optimal_subdomain_id,
2503  optimal_boundary_id,
2504  optimal_qp,
2505  optimal_point_perturbs,
2506  optimal_point_phi_i_qp);
2507 
2508  // In this case we did not encounter a linearly dependent basis function, so return false
2509  return false;
2510 }
2511 
2513  bool add_basis_function,
2514  EimPointData * eim_point_data)
2515 {
2516  // Make a copy of the input parametrized function, since we will modify this below
2517  // to give us a new basis function.
2518  NodeDataMap local_pf = node_pf;
2519 
2520  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2521 
2522  // If we have at least one basis function, then we need to use
2523  // rb_eim_solve() to find the EIM interpolation error. Otherwise,
2524  // just use solution as is.
2525  if (!eim_point_data && (eim_eval.get_n_basis_functions() > 0))
2526  {
2527  // Get the right-hand side vector for the EIM approximation
2528  // by sampling the parametrized function (stored in solution)
2529  // at the interpolation points.
2530  unsigned int RB_size = eim_eval.get_n_basis_functions();
2531  DenseVector<Number> EIM_rhs(RB_size);
2532  for (unsigned int i=0; i<RB_size; i++)
2533  {
2534  EIM_rhs(i) =
2536  local_pf,
2538  eim_eval.get_interpolation_points_comp(i));
2539  }
2540 
2541  eim_eval.set_parameters( get_parameters() );
2542  DenseVector<Number> rb_eim_solution = eim_eval.rb_eim_solve(EIM_rhs);
2543 
2544  // Load the "EIM residual" into solution by subtracting
2545  // the EIM approximation
2546  eim_eval.node_decrement_vector(local_pf, rb_eim_solution);
2547  }
2548 
2549  // Find the quadrature point at which local_pf (which now stores
2550  // the "EIM residual") has maximum absolute value
2551  Number optimal_value = 0.;
2552  Point optimal_point;
2553  unsigned int optimal_comp = 0;
2554  dof_id_type optimal_node_id = DofObject::invalid_id;
2555  boundary_id_type optimal_boundary_id = 0;
2556 
2557  // Initialize largest_abs_value to be negative so that it definitely gets updated.
2558  Real largest_abs_value = -1.;
2559 
2560  for (const auto & [node_id, values] : local_pf)
2561  {
2562  for (unsigned int comp : index_range(values))
2563  {
2564  Number value = values[comp];
2565  Real abs_value = std::abs(value);
2566 
2567  bool update_optimal_point = false;
2568  if (!eim_point_data)
2569  update_optimal_point = (abs_value > largest_abs_value);
2570  else
2571  update_optimal_point = (node_id == eim_point_data->node_id) &&
2572  (comp == eim_point_data->comp_index);
2573 
2574  if (update_optimal_point)
2575  {
2576  largest_abs_value = abs_value;
2577  optimal_value = value;
2578  optimal_comp = comp;
2579  optimal_node_id = node_id;
2580 
2581  optimal_point = libmesh_map_find(_local_node_locations, node_id);
2582 
2583  optimal_boundary_id = libmesh_map_find(_local_node_boundary_ids, node_id);
2584  }
2585  }
2586  }
2587 
2588  // Find out which processor has the largest of the abs values
2589  // and broadcast from that processor.
2590  unsigned int proc_ID_index;
2591  this->comm().maxloc(largest_abs_value, proc_ID_index);
2592 
2593  this->comm().broadcast(optimal_value, proc_ID_index);
2594  this->comm().broadcast(optimal_point, proc_ID_index);
2595  this->comm().broadcast(optimal_comp, proc_ID_index);
2596  this->comm().broadcast(optimal_node_id, proc_ID_index);
2597  this->comm().broadcast(optimal_boundary_id, proc_ID_index);
2598 
2599  libmesh_error_msg_if(optimal_node_id == DofObject::invalid_id, "Error: Invalid node ID");
2600 
2601  if (add_basis_function)
2602  {
2603  if (optimal_value == 0.)
2604  {
2605  libMesh::out << "Encountered linearly dependent data in EIM enrichment, hence skip adding new basis function" << std::endl;
2606  return true;
2607  }
2608 
2609  // Scale local_pf so that its largest value is 1.0
2610  scale_node_parametrized_function(local_pf, 1./optimal_value);
2611 
2612  // Add local_pf as the new basis function and store data
2613  // associated with the interpolation point.
2614  eim_eval.add_node_basis_function(local_pf);
2615  }
2616 
2617  eim_eval.add_node_interpolation_data(optimal_point,
2618  optimal_comp,
2619  optimal_node_id,
2620  optimal_boundary_id);
2621 
2622  // In this case we did not encounter a linearly dependent basis function, so return false
2623  return false;
2624 }
2625 
2627  bool add_basis_function,
2628  EimPointData * eim_point_data)
2629 {
2630  // Make a copy of the input parametrized function, since we will modify this below
2631  // to give us a new basis function.
2632  QpDataMap local_pf = interior_pf;
2633 
2634  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2635 
2636  // If we have at least one basis function, then we need to use
2637  // rb_eim_solve() to find the EIM interpolation error. Otherwise,
2638  // just use solution as is.
2639  if (!eim_point_data && (eim_eval.get_n_basis_functions() > 0))
2640  {
2641  // Get the right-hand side vector for the EIM approximation
2642  // by sampling the parametrized function (stored in solution)
2643  // at the interpolation points.
2644  unsigned int RB_size = eim_eval.get_n_basis_functions();
2645  DenseVector<Number> EIM_rhs(RB_size);
2646  for (unsigned int i=0; i<RB_size; i++)
2647  {
2648  EIM_rhs(i) =
2650  local_pf,
2652  eim_eval.get_interpolation_points_comp(i),
2653  eim_eval.get_interpolation_points_qp(i));
2654  }
2655 
2656  eim_eval.set_parameters( get_parameters() );
2657  DenseVector<Number> rb_eim_solution = eim_eval.rb_eim_solve(EIM_rhs);
2658 
2659  // Load the "EIM residual" into solution by subtracting
2660  // the EIM approximation
2661  eim_eval.decrement_vector(local_pf, rb_eim_solution);
2662  }
2663 
2664  // Find the quadrature point at which local_pf (which now stores
2665  // the "EIM residual") has maximum absolute value
2666  Number optimal_value = 0.;
2667  Point optimal_point;
2668  unsigned int optimal_comp = 0;
2669  dof_id_type optimal_elem_id = DofObject::invalid_id;
2670  subdomain_id_type optimal_subdomain_id = 0;
2671  unsigned int optimal_qp = 0;
2672  std::vector<Point> optimal_point_perturbs;
2673  std::vector<Real> optimal_point_phi_i_qp;
2674  ElemType optimal_elem_type = INVALID_ELEM;
2675  std::vector<Real> optimal_JxW_all_qp;
2676  std::vector<std::vector<Real>> optimal_phi_i_all_qp;
2677  Order optimal_qrule_order = INVALID_ORDER;
2678  Point optimal_dxyzdxi_elem_center;
2679  Point optimal_dxyzdeta_elem_center;
2680 
2681  // Initialize largest_abs_value to be negative so that it definitely gets updated.
2682  Real largest_abs_value = -1.;
2683 
2684  // In order to compute phi_i_qp, we initialize a FEMContext
2685  FEMContext con(*this);
2686  for (auto dim : con.elem_dimensions())
2687  {
2688  auto fe = con.get_element_fe(/*var=*/0, dim);
2689  fe->get_phi();
2690  fe->get_JxW();
2691  fe->get_dxyzdxi();
2692  fe->get_dxyzdeta();
2693  }
2694 
2695  for (const auto & [elem_id, comp_and_qp] : local_pf)
2696  {
2697  // Also initialize phi in order to compute phi_i_qp
2698  const Elem & elem_ref = get_mesh().elem_ref(elem_id);
2699  con.pre_fe_reinit(*this, &elem_ref);
2700 
2701  auto elem_fe = con.get_element_fe(/*var=*/0, elem_ref.dim());
2702  const std::vector<std::vector<Real>> & phi = elem_fe->get_phi();
2703  const auto & JxW = elem_fe->get_JxW();
2704  const auto & dxyzdxi = elem_fe->get_dxyzdxi();
2705  const auto & dxyzdeta = elem_fe->get_dxyzdeta();
2706 
2707  elem_fe->reinit(&elem_ref);
2708 
2709  for (const auto & comp : index_range(comp_and_qp))
2710  {
2711  const std::vector<Number> & qp_values = comp_and_qp[comp];
2712 
2713  for (auto qp : index_range(qp_values))
2714  {
2715  Number value = qp_values[qp];
2716  Real abs_value = std::abs(value);
2717 
2718  if (get_rb_eim_evaluation().scale_components_in_enrichment().count(comp))
2719  abs_value *= _component_scaling_in_training_set[comp];
2720 
2721  bool update_optimal_point = false;
2722  if (!eim_point_data)
2723  update_optimal_point = (abs_value > largest_abs_value);
2724  else
2725  update_optimal_point = (elem_id == eim_point_data->elem_id) &&
2726  (comp == eim_point_data->comp_index) &&
2727  (qp == eim_point_data->qp_index);
2728 
2729  if (update_optimal_point)
2730  {
2731  largest_abs_value = abs_value;
2732  optimal_value = value;
2733  optimal_comp = comp;
2734  optimal_elem_id = elem_id;
2735  optimal_qp = qp;
2736  optimal_elem_type = elem_ref.type();
2737 
2738  optimal_point_phi_i_qp.resize(phi.size());
2739  for (auto i : index_range(phi))
2740  optimal_point_phi_i_qp[i] = phi[i][qp];
2741 
2742  const auto & point_list =
2743  libmesh_map_find(_local_quad_point_locations, elem_id);
2744 
2745  libmesh_error_msg_if(qp >= point_list.size(), "Error: Invalid qp");
2746 
2747  optimal_point = point_list[qp];
2748 
2749  optimal_subdomain_id = libmesh_map_find(_local_quad_point_subdomain_ids, elem_id);
2750 
2751  if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
2752  {
2753  const auto & perturb_list =
2754  libmesh_map_find(_local_quad_point_locations_perturbations, elem_id);
2755 
2756  libmesh_error_msg_if(qp >= perturb_list.size(), "Error: Invalid qp");
2757 
2758  optimal_point_perturbs = perturb_list[qp];
2759  }
2760 
2762  {
2763  optimal_JxW_all_qp = JxW;
2764  optimal_phi_i_all_qp = phi;
2765  }
2766 
2768  {
2769  optimal_qrule_order = con.get_element_qrule().get_order();
2770  // Get data derivatives at vertex average
2771  std::vector<Point> nodes = { elem_ref.reference_elem()->vertex_average() };
2772  elem_fe->reinit (&elem_ref, &nodes);
2773 
2774  Point dxyzdxi_pt, dxyzdeta_pt;
2775  if (con.get_elem_dim()>0)
2776  dxyzdxi_pt = dxyzdxi[0];
2777  if (con.get_elem_dim()>1)
2778  dxyzdeta_pt = dxyzdeta[0];
2779 
2780  optimal_dxyzdxi_elem_center = dxyzdxi_pt;
2781  optimal_dxyzdeta_elem_center = dxyzdeta_pt;
2782 
2783  elem_fe->reinit(&elem_ref);
2784  }
2785  }
2786  }
2787  }
2788  }
2789 
2790  // Find out which processor has the largest of the abs values
2791  // and broadcast from that processor.
2792  unsigned int proc_ID_index;
2793  this->comm().maxloc(largest_abs_value, proc_ID_index);
2794 
2795  this->comm().broadcast(optimal_value, proc_ID_index);
2796  this->comm().broadcast(optimal_point, proc_ID_index);
2797  this->comm().broadcast(optimal_comp, proc_ID_index);
2798  this->comm().broadcast(optimal_elem_id, proc_ID_index);
2799  this->comm().broadcast(optimal_subdomain_id, proc_ID_index);
2800  this->comm().broadcast(optimal_qp, proc_ID_index);
2801  this->comm().broadcast(optimal_point_perturbs, proc_ID_index);
2802  this->comm().broadcast(optimal_point_phi_i_qp, proc_ID_index);
2803  this->comm().broadcast(optimal_JxW_all_qp, proc_ID_index);
2804  this->comm().broadcast(optimal_phi_i_all_qp, proc_ID_index);
2805  this->comm().broadcast(optimal_dxyzdxi_elem_center, proc_ID_index);
2806  this->comm().broadcast(optimal_dxyzdeta_elem_center, proc_ID_index);
2807 
2808  // Cast optimal_elem_type to an int in order to broadcast it
2809  {
2810  int optimal_elem_type_int = static_cast<int>(optimal_elem_type);
2811  this->comm().broadcast(optimal_elem_type_int, proc_ID_index);
2812  optimal_elem_type = static_cast<ElemType>(optimal_elem_type_int);
2813  }
2814 
2815  // Cast optimal_qrule_order to an int in order to broadcast it
2816  {
2817  int optimal_qrule_order_int = static_cast<int>(optimal_qrule_order);
2818  this->comm().broadcast(optimal_qrule_order_int, proc_ID_index);
2819  optimal_qrule_order = static_cast<Order>(optimal_qrule_order_int);
2820  }
2821 
2822  libmesh_error_msg_if(optimal_elem_id == DofObject::invalid_id, "Error: Invalid element ID");
2823 
2824  if (add_basis_function)
2825  {
2826  if (optimal_value == 0.)
2827  {
2828  libMesh::out << "Encountered linearly dependent data in EIM enrichment, hence skip adding new basis function" << std::endl;
2829  return true;
2830  }
2831 
2832  // Scale local_pf so that its largest value is 1.0
2833  scale_parametrized_function(local_pf, 1./optimal_value);
2834 
2835  // Add local_pf as the new basis function and store data
2836  // associated with the interpolation point.
2837  eim_eval.add_basis_function(local_pf);
2838  }
2839 
2840  eim_eval.add_interpolation_data(optimal_point,
2841  optimal_comp,
2842  optimal_elem_id,
2843  optimal_subdomain_id,
2844  optimal_qp,
2845  optimal_point_perturbs,
2846  optimal_point_phi_i_qp,
2847  optimal_elem_type,
2848  optimal_JxW_all_qp,
2849  optimal_phi_i_all_qp,
2850  optimal_qrule_order,
2851  optimal_dxyzdxi_elem_center,
2852  optimal_dxyzdeta_elem_center);
2853 
2854  // In this case we did not encounter a linearly dependent basis function, so return false
2855  return false;
2856 }
2857 
2858 void RBEIMConstruction::update_eim_matrices(bool set_eim_error_indicator)
2859 {
2860  LOG_SCOPE("update_eim_matrices()", "RBEIMConstruction");
2861 
2862  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
2863  unsigned int RB_size = eim_eval.get_n_basis_functions();
2864 
2865  libmesh_assert_msg(RB_size >= 1, "Must have at least 1 basis function.");
2866 
2867  if (set_eim_error_indicator)
2868  {
2869  // Here we have RB_size EIM basis functions, and RB_size+1 interpolation points,
2870  // since we should have added one extra interpolation point for the EIM error
2871  // indicator. As a result, we use RB_size as the index to access the (RB_size+1)^th
2872  // interpolation point in the calls to eim_eval.get_interpolation_points_*.
2873  DenseVector<Number> extra_point_row(RB_size);
2874 
2875  if (eim_eval.get_parametrized_function().on_mesh_sides())
2876  {
2877  // update the EIM interpolation matrix
2878  for (unsigned int j=0; j<RB_size; j++)
2879  {
2880  // Evaluate the basis functions at the new interpolation point in order
2881  // to update the interpolation matrix
2882  Number value =
2884  eim_eval.get_interpolation_points_elem_id(RB_size),
2885  eim_eval.get_interpolation_points_side_index(RB_size),
2886  eim_eval.get_interpolation_points_comp(RB_size),
2887  eim_eval.get_interpolation_points_qp(RB_size));
2888  extra_point_row(j) = value;
2889  }
2890  }
2891  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
2892  {
2893  // update the EIM interpolation matrix
2894  for (unsigned int j=0; j<RB_size; j++)
2895  {
2896  // Evaluate the basis functions at the new interpolation point in order
2897  // to update the interpolation matrix
2898  Number value =
2900  eim_eval.get_interpolation_points_node_id(RB_size),
2901  eim_eval.get_interpolation_points_comp(RB_size));
2902  extra_point_row(j) = value;
2903  }
2904  }
2905  else
2906  {
2907  // update the EIM interpolation matrix
2908  for (unsigned int j=0; j<RB_size; j++)
2909  {
2910  // Evaluate the basis functions at the new interpolation point in order
2911  // to update the interpolation matrix
2912  Number value =
2913  eim_eval.get_eim_basis_function_value(j,
2914  eim_eval.get_interpolation_points_elem_id(RB_size),
2915  eim_eval.get_interpolation_points_comp(RB_size),
2916  eim_eval.get_interpolation_points_qp(RB_size));
2917  extra_point_row(j) = value;
2918  }
2919  }
2920 
2921  eim_eval.set_error_indicator_interpolation_row(extra_point_row);
2922  return;
2923  }
2924 
2925  if (eim_eval.get_parametrized_function().on_mesh_sides())
2926  {
2927  // update the matrix that is used to evaluate L2 projections
2928  // into the EIM approximation space
2929  for (unsigned int i=(RB_size-1); i<RB_size; i++)
2930  {
2931  for (unsigned int j=0; j<RB_size; j++)
2932  {
2934  eim_eval.get_side_basis_function(i),
2935  /*apply_comp_scaling*/ false);
2936 
2938  if (i!=j)
2939  {
2940  // The inner product matrix is assumed to be hermitian
2942  }
2943  }
2944  }
2945 
2946  // update the EIM interpolation matrix
2947  for (unsigned int j=0; j<RB_size; j++)
2948  {
2949  // Evaluate the basis functions at the new interpolation point in order
2950  // to update the interpolation matrix
2951  Number value =
2953  eim_eval.get_interpolation_points_elem_id(RB_size-1),
2954  eim_eval.get_interpolation_points_side_index(RB_size-1),
2955  eim_eval.get_interpolation_points_comp(RB_size-1),
2956  eim_eval.get_interpolation_points_qp(RB_size-1));
2957  eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
2958  }
2959  }
2960  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
2961  {
2962  // update the matrix that is used to evaluate L2 projections
2963  // into the EIM approximation space
2964  for (unsigned int i=(RB_size-1); i<RB_size; i++)
2965  {
2966  for (unsigned int j=0; j<RB_size; j++)
2967  {
2969  eim_eval.get_node_basis_function(i),
2970  /*apply_comp_scaling*/ false);
2971 
2973  if (i!=j)
2974  {
2975  // The inner product matrix is assumed to be hermitian
2977  }
2978  }
2979  }
2980 
2981  // update the EIM interpolation matrix
2982  for (unsigned int j=0; j<RB_size; j++)
2983  {
2984  // Evaluate the basis functions at the new interpolation point in order
2985  // to update the interpolation matrix
2986  Number value =
2988  eim_eval.get_interpolation_points_node_id(RB_size-1),
2989  eim_eval.get_interpolation_points_comp(RB_size-1));
2990  eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
2991  }
2992  }
2993  else
2994  {
2995  // update the matrix that is used to evaluate L2 projections
2996  // into the EIM approximation space
2997  for (unsigned int i=(RB_size-1); i<RB_size; i++)
2998  {
2999  for (unsigned int j=0; j<RB_size; j++)
3000  {
3002  eim_eval.get_basis_function(i),
3003  /*apply_comp_scaling*/ false);
3004 
3006  if (i!=j)
3007  {
3008  // The inner product matrix is assumed to be hermitian
3010  }
3011  }
3012  }
3013 
3014  // update the EIM interpolation matrix
3015  for (unsigned int j=0; j<RB_size; j++)
3016  {
3017  // Evaluate the basis functions at the new interpolation point in order
3018  // to update the interpolation matrix
3019  Number value =
3020  eim_eval.get_eim_basis_function_value(j,
3021  eim_eval.get_interpolation_points_elem_id(RB_size-1),
3022  eim_eval.get_interpolation_points_comp(RB_size-1),
3023  eim_eval.get_interpolation_points_qp(RB_size-1));
3024  eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
3025  }
3026  }
3027 }
3028 
3030  Number scaling_factor)
3031 {
3032  for (auto & pr : local_pf)
3033  {
3034  auto & values = pr.second;
3035  for ( auto & value : values)
3036  value *= scaling_factor;
3037  }
3038 }
3039 
3040 unsigned int RBEIMConstruction::get_random_int_0_to_n(unsigned int n)
3041 {
3042  // std::random_device seed;
3043  // std::mt19937 gen{seed()};
3044  // We do not use a random seed here, since we generally prefer our results
3045  // to reproducible, rather than fully random. If desired we could provide an
3046  // option to use the random seed approach (commented out above).
3047  std::default_random_engine gen;
3048  std::uniform_int_distribution<> dist{0, static_cast<int>(n)};
3049  return dist(gen);
3050 }
3051 
3053 {
3054  EimPointData eim_point_data;
3055 
3056  // If we have more than one process, then we need to do a parallel union
3057  // of v to make sure that we have data from all processors. Our approach
3058  // here is to set v_ptr to either v or global_v, depending on whether we
3059  // are in parallel or serial. The purpose of this approach is to avoid
3060  // making a copy of v in the case that this is a serial job.
3061  QpDataMap const * v_ptr = nullptr;
3062  QpDataMap global_v;
3063  if (comm().size() > 1)
3064  {
3065  global_v = v;
3066 
3067  // We only use global_v on proc 0, so we set the second argument of
3068  // set_union() to zero here to indicate that we only need the result
3069  // on proc 0.
3070  comm().set_union(global_v, 0);
3071  v_ptr = &global_v;
3072  }
3073  else
3074  {
3075  v_ptr = &v;
3076  }
3077 
3078  bool error_finding_new_element = false;
3079  if (comm().rank() == 0)
3080  {
3081  const VectorizedEvalInput & vec_eval_input = get_rb_eim_evaluation().get_vec_eval_input();
3082 
3083  {
3084  std::set<dof_id_type> previous_elem_ids(vec_eval_input.elem_ids.begin(), vec_eval_input.elem_ids.end());
3085 
3086  // We ensure that we select a point that has not been selected previously
3087  // by setting up new_elem_ids to contain only elements that are not in
3088  // previous_elem_ids, and then selecting the elem_id at random from new_elem_ids.
3089  // We give an error if there are no elements in new_elem_ids. This is potentially
3090  // an overzealous assertion since we could pick an element that has already
3091  // been selected as long as we pick a (comp_index, qp_index) that has not already
3092  // been selected for that element.
3093  //
3094  // However, in general we do not expect all elements to be selected in the EIM
3095  // training, so it is reasonable to use the simple assertion below. Moreover, by
3096  // ensuring that we choose a new element we should typically ensure that the
3097  // randomly selected point has some separation from the previous EIM points, which
3098  // is typically desirable if we want EIM evaluations that are independent from
3099  // the EIM points (e.g. for EIM error indicator purposes).
3100  std::set<dof_id_type> new_elem_ids;
3101  for (const auto & v_pair : *v_ptr)
3102  if (previous_elem_ids.count(v_pair.first) == 0)
3103  new_elem_ids.insert(v_pair.first);
3104 
3105  // If new_elem_ids is empty then we set error_finding_new_element to true.
3106  // We then broadcast the value of error_finding_new_element to all processors
3107  // below in order to ensure that all processors agree on whether or not
3108  // there was an error.
3109  error_finding_new_element = (new_elem_ids.empty());
3110 
3111  if (!error_finding_new_element)
3112  {
3113  unsigned int random_elem_idx = get_random_int_0_to_n(new_elem_ids.size()-1);
3114 
3115  auto item = new_elem_ids.begin();
3116  std::advance(item, random_elem_idx);
3117  eim_point_data.elem_id = *item;
3118  }
3119  }
3120 
3121  if (!error_finding_new_element)
3122  {
3123  {
3124  const auto & vars_and_qps = libmesh_map_find(*v_ptr,eim_point_data.elem_id);
3125  eim_point_data.comp_index = get_random_int_0_to_n(vars_and_qps.size()-1);
3126  }
3127 
3128  {
3129  const auto & qps = libmesh_map_find(*v_ptr,eim_point_data.elem_id)[eim_point_data.comp_index];
3130  eim_point_data.qp_index = get_random_int_0_to_n(qps.size()-1);
3131  }
3132  }
3133  }
3134 
3135  comm().broadcast(error_finding_new_element);
3136  libmesh_error_msg_if(error_finding_new_element, "Could not find new element in get_random_point()");
3137 
3138  // Broadcast the values computed above from rank 0
3139  comm().broadcast(eim_point_data.elem_id);
3140  comm().broadcast(eim_point_data.comp_index);
3141  comm().broadcast(eim_point_data.qp_index);
3142 
3143  return eim_point_data;
3144 }
3145 
3147 {
3148  EimPointData eim_point_data;
3149 
3150  // If we have more than one process, then we need to do a parallel union
3151  // of v to make sure that we have data from all processors. Our approach
3152  // here is to set v_ptr to either v or global_v, depending on whether we
3153  // are in parallel or serial. The purpose of this approach is to avoid
3154  // making a copy of v in the case that this is a serial job.
3155  SideQpDataMap const * v_ptr = nullptr;
3156  SideQpDataMap global_v;
3157  if (comm().size() > 1)
3158  {
3159  global_v = v;
3160 
3161  // We only use global_v on proc 0, so we set the second argument of
3162  // set_union() to zero here to indicate that we only need the result
3163  // on proc 0.
3164  comm().set_union(global_v, 0);
3165  v_ptr = &global_v;
3166  }
3167  else
3168  {
3169  v_ptr = &v;
3170  }
3171 
3172  bool error_finding_new_element_and_side = false;
3173  if (comm().rank() == 0)
3174  {
3175  const VectorizedEvalInput & vec_eval_input = get_rb_eim_evaluation().get_vec_eval_input();
3176 
3177  std::pair<dof_id_type,unsigned int> elem_and_side;
3178  {
3179  std::set<std::pair<dof_id_type,unsigned int>> previous_elem_and_side_ids;
3180  for (const auto idx : index_range(vec_eval_input.elem_ids))
3181  {
3182  previous_elem_and_side_ids.insert(
3183  std::make_pair(vec_eval_input.elem_ids[idx],
3184  vec_eval_input.side_indices[idx]));
3185  }
3186 
3187  // See discussion above in the QpDataMap case for the justification
3188  // of how we set up new_elem_and_side_ids below.
3189  std::set<std::pair<dof_id_type,unsigned int>> new_elem_and_side_ids;
3190  for (const auto & v_pair : *v_ptr)
3191  if (previous_elem_and_side_ids.count(v_pair.first) == 0)
3192  new_elem_and_side_ids.insert(v_pair.first);
3193 
3194  // If new_elem_and_side_ids is empty then we set error_finding_new_element_and_side
3195  // to true. We then broadcast the value of error_finding_new_element_and_side to all
3196  // processors below in order to ensure that all processors agree on whether
3197  // or not there was an error.
3198  error_finding_new_element_and_side = (new_elem_and_side_ids.empty());
3199 
3200  if (!error_finding_new_element_and_side)
3201  {
3202  unsigned int random_elem_and_side_idx = get_random_int_0_to_n(new_elem_and_side_ids.size()-1);
3203 
3204  auto item = new_elem_and_side_ids.begin();
3205  std::advance(item, random_elem_and_side_idx);
3206  elem_and_side = *item;
3207  eim_point_data.elem_id = elem_and_side.first;
3208  eim_point_data.side_index = elem_and_side.second;
3209  }
3210  }
3211 
3212  if (!error_finding_new_element_and_side)
3213  {
3214  {
3215  const auto & vars_and_qps = libmesh_map_find(*v_ptr,elem_and_side);
3216  eim_point_data.comp_index = get_random_int_0_to_n(vars_and_qps.size()-1);
3217  }
3218 
3219  {
3220  const auto & qps = libmesh_map_find(*v_ptr,elem_and_side)[eim_point_data.comp_index];
3221  eim_point_data.qp_index = get_random_int_0_to_n(qps.size()-1);
3222  }
3223  }
3224  }
3225 
3226  comm().broadcast(error_finding_new_element_and_side);
3227  libmesh_error_msg_if(error_finding_new_element_and_side, "Could not find new (element,side) in get_random_point()");
3228 
3229  // Broadcast the values computed above from rank 0
3230  comm().broadcast(eim_point_data.elem_id);
3231  comm().broadcast(eim_point_data.side_index);
3232  comm().broadcast(eim_point_data.comp_index);
3233  comm().broadcast(eim_point_data.qp_index);
3234 
3235  return eim_point_data;
3236 }
3237 
3239 {
3240  EimPointData eim_point_data;
3241 
3242  // If we have more than one process, then we need to do a parallel union
3243  // of v to make sure that we have data from all processors. Our approach
3244  // here is to set v_ptr to either v or global_v, depending on whether we
3245  // are in parallel or serial. The purpose of this approach is to avoid
3246  // making a copy of v in the case that this is a serial job.
3247  NodeDataMap const * v_ptr = nullptr;
3248  NodeDataMap global_v;
3249  if (comm().size() > 1)
3250  {
3251  global_v = v;
3252 
3253  // We only use global_v on proc 0, so we set the second argument of
3254  // set_union() to zero here to indicate that we only need the result
3255  // on proc 0.
3256  comm().set_union(global_v, 0);
3257  v_ptr = &global_v;
3258  }
3259  else
3260  {
3261  v_ptr = &v;
3262  }
3263 
3264  bool error_finding_new_node = false;
3265  if (comm().rank() == 0)
3266  {
3267  const VectorizedEvalInput & vec_eval_input = get_rb_eim_evaluation().get_vec_eval_input();
3268 
3269  {
3270  std::set<dof_id_type> previous_node_ids(vec_eval_input.node_ids.begin(), vec_eval_input.node_ids.end());
3271 
3272  // See discussion above in the QpDataMap case for the justification
3273  // of how we set up new_node_ids below.
3274  std::set<dof_id_type> new_node_ids;
3275  for (const auto & v_pair : *v_ptr)
3276  if (previous_node_ids.count(v_pair.first) == 0)
3277  new_node_ids.insert(v_pair.first);
3278 
3279  // If new_node_ids is empty then we set error_finding_new_node
3280  // to true. We then broadcast the value of error_finding_new_node to all
3281  // processors below in order to ensure that all processors agree on whether
3282  // or not there was an error.
3283  error_finding_new_node = (new_node_ids.empty());
3284 
3285  if (!error_finding_new_node)
3286  {
3287  unsigned int random_node_idx = get_random_int_0_to_n(new_node_ids.size()-1);
3288 
3289  auto item = new_node_ids.begin();
3290  std::advance(item, random_node_idx);
3291  eim_point_data.node_id = *item;
3292  }
3293  }
3294 
3295  if (!error_finding_new_node)
3296  {
3297  const auto & vars = libmesh_map_find(*v_ptr,eim_point_data.node_id);
3298  eim_point_data.comp_index = get_random_int_0_to_n(vars.size()-1);
3299  }
3300  }
3301 
3302  comm().broadcast(error_finding_new_node);
3303  libmesh_error_msg_if(error_finding_new_node, "Could not find new node in get_random_point()");
3304 
3305  // Broadcast the values computed above from rank 0
3306  comm().broadcast(eim_point_data.node_id);
3307  comm().broadcast(eim_point_data.comp_index);
3308 
3309  return eim_point_data;
3310 }
3311 
3313 {
3314  RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
3315 
3316  if (eim_eval.get_parametrized_function().on_mesh_sides())
3318  else if (eim_eval.get_parametrized_function().on_mesh_nodes())
3320  else
3322 }
3323 
3324 } // 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:454
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:2358
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:570
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:2598
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:55
virtual const Elem & elem_ref(const dof_id_type i) const
Definition: mesh_base.h:639
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:74
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:2342
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:2430
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:688
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.