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 #include "libmesh/auto_ptr.h"
45 
46 // rbOOmit includes
47 #include "libmesh/rb_eim_construction.h"
48 #include "libmesh/rb_eim_evaluation.h"
49 #include "libmesh/rb_parametrized_function.h"
50 
51 namespace libMesh
52 {
53 
RBEIMConstruction(EquationSystems & es,const std::string & name_in,const unsigned int number_in)54 RBEIMConstruction::RBEIMConstruction (EquationSystems & es,
55                                       const std::string & name_in,
56                                       const unsigned int number_in)
57   : RBConstructionBase(es, name_in, number_in),
58     best_fit_type_flag(PROJECTION_BEST_FIT),
59     _Nmax(0),
60     _rel_training_tolerance(1.e-4),
61     _abs_training_tolerance(1.e-12),
62     _max_abs_value_in_training_set(0.)
63 {
64   // The training set should be the same on all processors in the
65   // case of EIM training.
66   serial_training_set = true;
67 }
68 
69 RBEIMConstruction::~RBEIMConstruction () = default;
70 
clear()71 void RBEIMConstruction::clear()
72 {
73   RBConstructionBase::clear();
74 
75   _rb_eim_assembly_objects.clear();
76 
77   _local_parametrized_functions_for_training.clear();
78   _local_quad_point_locations.clear();
79   _local_quad_point_JxW.clear();
80   _local_quad_point_subdomain_ids.clear();
81 
82   _eim_projection_matrix.resize(0,0);
83 }
84 
set_rb_eim_evaluation(RBEIMEvaluation & rb_eim_eval_in)85 void RBEIMConstruction::set_rb_eim_evaluation(RBEIMEvaluation & rb_eim_eval_in)
86 {
87   _rb_eim_eval = &rb_eim_eval_in;
88 }
89 
get_rb_eim_evaluation()90 RBEIMEvaluation & RBEIMConstruction::get_rb_eim_evaluation()
91 {
92   libmesh_error_msg_if(!_rb_eim_eval, "Error: RBEIMEvaluation object hasn't been initialized yet");
93 
94   return *_rb_eim_eval;
95 }
96 
set_best_fit_type_flag(const std::string & best_fit_type_string)97 void RBEIMConstruction::set_best_fit_type_flag (const std::string & best_fit_type_string)
98 {
99   if (best_fit_type_string == "projection")
100     {
101       best_fit_type_flag = PROJECTION_BEST_FIT;
102     }
103   else
104     if (best_fit_type_string == "eim")
105       {
106         best_fit_type_flag = EIM_BEST_FIT;
107       }
108     else
109       libmesh_error_msg("Error: invalid best_fit_type in input file");
110 }
111 
print_info()112 void RBEIMConstruction::print_info()
113 {
114   // Print out info that describes the current setup
115   libMesh::out << std::endl << "RBEIMConstruction parameters:" << std::endl;
116   libMesh::out << "system name: " << this->name() << std::endl;
117   libMesh::out << "Nmax: " << get_Nmax() << std::endl;
118   libMesh::out << "Greedy relative error tolerance: " << get_rel_training_tolerance() << std::endl;
119   libMesh::out << "Greedy absolute error tolerance: " << get_abs_training_tolerance() << std::endl;
120   libMesh::out << "Number of parameters: " << get_n_params() << std::endl;
121   for (const auto & pr : get_parameters())
122     if (!is_discrete_parameter(pr.first))
123       {
124         libMesh::out <<   "Parameter " << pr.first
125                      << ": Min = " << get_parameter_min(pr.first)
126                      << ", Max = " << get_parameter_max(pr.first) << std::endl;
127       }
128 
129   print_discrete_parameter_values();
130   libMesh::out << "n_training_samples: " << get_n_training_samples() << std::endl;
131   libMesh::out << "quiet mode? " << is_quiet() << std::endl;
132 
133   if (best_fit_type_flag == PROJECTION_BEST_FIT)
134     {
135       libMesh::out << "EIM best fit type: projection" << std::endl;
136     }
137   else
138     if (best_fit_type_flag == EIM_BEST_FIT)
139       {
140         libMesh::out << "EIM best fit type: eim" << std::endl;
141       }
142   libMesh::out << std::endl;
143 }
144 
initialize_eim_construction()145 void RBEIMConstruction::initialize_eim_construction()
146 {
147   initialize_parametrized_functions_in_training_set();
148 }
149 
process_parameters_file(const std::string & parameters_filename)150 void RBEIMConstruction::process_parameters_file (const std::string & parameters_filename)
151 {
152   // First read in data from input_filename
153   GetPot infile(parameters_filename);
154 
155   std::string best_fit_type_string = infile("best_fit_type","projection");
156   set_best_fit_type_flag(best_fit_type_string);
157 
158   const unsigned int n_training_samples = infile("n_training_samples",0);
159   const bool deterministic_training = infile("deterministic_training",false);
160   unsigned int training_parameters_random_seed_in =
161     static_cast<unsigned int>(-1);
162   training_parameters_random_seed_in = infile("training_parameters_random_seed",
163                                               training_parameters_random_seed_in);
164   const bool quiet_mode_in = infile("quiet_mode", quiet_mode);
165   const unsigned int Nmax_in = infile("Nmax", _Nmax);
166   const Real rel_training_tolerance_in = infile("rel_training_tolerance",
167                                                 _rel_training_tolerance);
168   const Real abs_training_tolerance_in = infile("abs_training_tolerance",
169                                                 _abs_training_tolerance);
170 
171   // Read in the parameters from the input file too
172   unsigned int n_continuous_parameters = infile.vector_variable_size("parameter_names");
173   RBParameters mu_min_in;
174   RBParameters mu_max_in;
175   for (unsigned int i=0; i<n_continuous_parameters; i++)
176     {
177       // Read in the parameter names
178       std::string param_name = infile("parameter_names", "NONE", i);
179 
180       {
181         Real min_val = infile(param_name, 0., 0);
182         mu_min_in.set_value(param_name, min_val);
183       }
184 
185       {
186         Real max_val = infile(param_name, 0., 1);
187         mu_max_in.set_value(param_name, max_val);
188       }
189     }
190 
191   std::map<std::string, std::vector<Real>> discrete_parameter_values_in;
192 
193   unsigned int n_discrete_parameters = infile.vector_variable_size("discrete_parameter_names");
194   for (unsigned int i=0; i<n_discrete_parameters; i++)
195     {
196       std::string param_name = infile("discrete_parameter_names", "NONE", i);
197 
198       unsigned int n_vals_for_param = infile.vector_variable_size(param_name);
199       std::vector<Real> vals_for_param(n_vals_for_param);
200       for (auto j : make_range(vals_for_param.size()))
201         vals_for_param[j] = infile(param_name, 0., j);
202 
203       discrete_parameter_values_in[param_name] = vals_for_param;
204     }
205 
206   std::map<std::string,bool> log_scaling_in;
207   // For now, just set all entries to false.
208   // TODO: Implement a decent way to specify log-scaling true/false
209   // in the input text file
210   for (const auto & pr : mu_min_in)
211     log_scaling_in[pr.first] = false;
212 
213   // Set the parameters that have been read in
214   set_rb_construction_parameters(n_training_samples,
215                                  deterministic_training,
216                                  training_parameters_random_seed_in,
217                                  quiet_mode_in,
218                                  Nmax_in,
219                                  rel_training_tolerance_in,
220                                  abs_training_tolerance_in,
221                                  mu_min_in,
222                                  mu_max_in,
223                                  discrete_parameter_values_in,
224                                  log_scaling_in);
225 }
226 
set_rb_construction_parameters(unsigned int n_training_samples_in,bool deterministic_training_in,unsigned int training_parameters_random_seed_in,bool quiet_mode_in,unsigned int Nmax_in,Real rel_training_tolerance_in,Real abs_training_tolerance_in,RBParameters mu_min_in,RBParameters mu_max_in,std::map<std::string,std::vector<Real>> discrete_parameter_values_in,std::map<std::string,bool> log_scaling_in)227 void RBEIMConstruction::set_rb_construction_parameters(unsigned int n_training_samples_in,
228                                                        bool deterministic_training_in,
229                                                        unsigned int training_parameters_random_seed_in,
230                                                        bool quiet_mode_in,
231                                                        unsigned int Nmax_in,
232                                                        Real rel_training_tolerance_in,
233                                                        Real abs_training_tolerance_in,
234                                                        RBParameters mu_min_in,
235                                                        RBParameters mu_max_in,
236                                                        std::map<std::string, std::vector<Real>> discrete_parameter_values_in,
237                                                        std::map<std::string,bool> log_scaling_in)
238 {
239   // Read in training_parameters_random_seed value.  This is used to
240   // seed the RNG when picking the training parameters.  By default the
241   // value is -1, which means use std::time to seed the RNG.
242   set_training_random_seed(training_parameters_random_seed_in);
243 
244   // Set quiet mode
245   set_quiet_mode(quiet_mode_in);
246 
247   // Initialize RB parameters
248   set_Nmax(Nmax_in);
249 
250   set_rel_training_tolerance(rel_training_tolerance_in);
251   set_abs_training_tolerance(abs_training_tolerance_in);
252 
253   if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table)
254     {
255       const std::string & lookup_table_param_name =
256         get_rb_eim_evaluation().get_parametrized_function().lookup_table_param_name;
257 
258       libmesh_error_msg_if(!discrete_parameter_values_in.count(lookup_table_param_name),
259         "Lookup table parameter should be discrete");
260 
261       std::vector<Real> & lookup_table_param_values =
262         libmesh_map_find(discrete_parameter_values_in, lookup_table_param_name);
263 
264       // Overwrite the discrete values for lookup_table_param to make sure that
265       // it is: 0, 1, 2, ..., size-1.
266       std::iota(lookup_table_param_values.begin(), lookup_table_param_values.end(), 0);
267 
268       // Also, overwrite n_training_samples_in to make sure it matches
269       // lookup_table_size so that we will get full coverage of the
270       // lookup table in our training set.
271       n_training_samples_in = lookup_table_param_values.size();
272     }
273 
274   // Initialize the parameter ranges and the parameters themselves
275   initialize_parameters(mu_min_in, mu_max_in, discrete_parameter_values_in);
276 
277   initialize_training_parameters(this->get_parameters_min(),
278                                  this->get_parameters_max(),
279                                  n_training_samples_in,
280                                  log_scaling_in,
281                                  deterministic_training_in);
282 
283   if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table)
284     {
285       // Also, now that we've initialized the training set, overwrite the training
286       // samples to ensure that we have full coverage of the lookup tbale.
287       const std::string & lookup_table_param_name =
288         get_rb_eim_evaluation().get_parametrized_function().lookup_table_param_name;
289 
290       std::vector<Number> lookup_table_training_samples(n_training_samples_in);
291       std::iota(lookup_table_training_samples.begin(), lookup_table_training_samples.end(), 0);
292 
293       set_training_parameter_values(lookup_table_param_name, lookup_table_training_samples);
294     }
295 }
296 
train_eim_approximation()297 Real RBEIMConstruction::train_eim_approximation()
298 {
299   LOG_SCOPE("train_eim_approximation()", "RBConstruction");
300 
301   _eim_projection_matrix.resize(get_Nmax(),get_Nmax());
302 
303   RBEIMEvaluation & rbe = get_rb_eim_evaluation();
304   rbe.initialize_parameters(*this);
305   rbe.resize_data_structures(get_Nmax());
306 
307   // If we are continuing from a previous training run,
308   // we might already be at the max number of basis functions.
309   // If so, we can just return.
310   libmesh_error_msg_if(rbe.get_n_basis_functions() > 0,
311                        "Error: We currently only support EIM training starting from an empty basis");
312 
313   libMesh::out << std::endl << "---- Performing Greedy EIM basis enrichment ----" << std::endl;
314   Real abs_greedy_error = 0.;
315   Real initial_greedy_error = 0.;
316   bool initial_greedy_error_initialized = false;
317   std::vector<RBParameters> greedy_param_list;
318   greedy_param_list.emplace_back();
319   unsigned int current_training_index = 0;
320   set_params_from_training_set(current_training_index);
321   while (true)
322     {
323       libMesh::out << "Greedily selected parameter vector:" << std::endl;
324       print_parameters();
325       greedy_param_list.emplace_back(get_parameters());
326 
327       libMesh::out << "Enriching the EIM approximation" << std::endl;
328       enrich_eim_approximation(current_training_index);
329       update_eim_matrices();
330 
331       libMesh::out << std::endl << "---- Basis dimension: "
332                    << rbe.get_n_basis_functions() << " ----" << std::endl;
333 
334       libMesh::out << "Computing EIM error on training set" << std::endl;
335       std::pair<Real,unsigned int> max_error_pair = compute_max_eim_error();
336       abs_greedy_error = max_error_pair.first;
337       current_training_index = max_error_pair.second;
338       set_params_from_training_set(current_training_index);
339 
340       libMesh::out << "Maximum EIM error is " << abs_greedy_error << std::endl << std::endl;
341 
342       // record the initial error
343       if (!initial_greedy_error_initialized)
344         {
345           initial_greedy_error = abs_greedy_error;
346           initial_greedy_error_initialized = true;
347         }
348 
349       // Convergence and/or termination tests
350       {
351         if (rbe.get_n_basis_functions() >= this->get_Nmax())
352           {
353             libMesh::out << "Maximum number of basis functions reached: Nmax = "
354                           << get_Nmax() << std::endl;
355             break;
356           }
357 
358         // We scale the absolution tolerance by the maximum absolute value in our
359         // training set, because otherwise users would need to know the magnitude
360         // of the functions being considered before setting the absolute tolerance.
361         // It is more reliable to compute the relevant magnitude and scale the tolerance
362         // by that magnitude.
363         if (abs_greedy_error < (get_abs_training_tolerance() * get_max_abs_value_in_training_set()))
364           {
365             libMesh::out << "Absolute error tolerance reached." << std::endl;
366             break;
367           }
368 
369         Real rel_greedy_error = abs_greedy_error/initial_greedy_error;
370         if (rel_greedy_error < get_rel_training_tolerance())
371           {
372             libMesh::out << "Relative error tolerance reached." << std::endl;
373             break;
374           }
375 
376         if (rbe.get_n_basis_functions() >= this->get_Nmax())
377           {
378             libMesh::out << "Maximum number of basis functions reached: Nmax = "
379                          << get_Nmax() << std::endl;
380             break;
381           }
382 
383         {
384           bool do_exit = false;
385           for (auto & param : greedy_param_list)
386             if (param == get_parameters())
387               {
388                 libMesh::out << "Exiting greedy because the same parameters were selected twice"
389                              << std::endl;
390                 do_exit = true;
391                 break;
392               }
393 
394           if (do_exit)
395             break; // out of while
396         }
397       }
398     } // end while(true)
399 
400   if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table)
401     {
402       store_eim_solutions_for_training_set();
403     }
404 
405   return abs_greedy_error;
406 }
407 
initialize_eim_assembly_objects()408 void RBEIMConstruction::initialize_eim_assembly_objects()
409 {
410   _rb_eim_assembly_objects.clear();
411   for (auto i : make_range(get_rb_eim_evaluation().get_n_basis_functions()))
412     _rb_eim_assembly_objects.push_back(build_eim_assembly(i));
413 }
414 
get_eim_assembly_objects()415 std::vector<std::unique_ptr<ElemAssembly>> & RBEIMConstruction::get_eim_assembly_objects()
416 {
417   return _rb_eim_assembly_objects;
418 }
419 
init_context(FEMContext & c)420 void RBEIMConstruction::init_context(FEMContext & c)
421 {
422   // Pre-request FE data for all element dimensions present in the
423   // mesh.  Note: we currently pre-request FE data for all variables
424   // in the current system but in some cases that may be overkill, for
425   // example if only variable 0 is used.
426   const System & sys = c.get_system();
427   const MeshBase & mesh = sys.get_mesh();
428 
429   for (unsigned int dim=1; dim<=3; ++dim)
430     if (mesh.elem_dimensions().count(dim))
431       for (auto var : make_range(sys.n_vars()))
432       {
433         auto fe = c.get_element_fe(var, dim);
434         fe->get_JxW();
435         fe->get_xyz();
436 
437         auto side_fe = c.get_side_fe(var, dim);
438         side_fe->get_JxW();
439         side_fe->get_xyz();
440       }
441 }
442 
set_rel_training_tolerance(Real new_training_tolerance)443 void RBEIMConstruction::set_rel_training_tolerance(Real new_training_tolerance)
444 {
445   _rel_training_tolerance = new_training_tolerance;
446 }
447 
get_rel_training_tolerance()448 Real RBEIMConstruction::get_rel_training_tolerance()
449 {
450   return _rel_training_tolerance;
451 }
452 
set_abs_training_tolerance(Real new_training_tolerance)453 void RBEIMConstruction::set_abs_training_tolerance(Real new_training_tolerance)
454 {
455   _abs_training_tolerance = new_training_tolerance;
456 }
457 
get_abs_training_tolerance()458 Real RBEIMConstruction::get_abs_training_tolerance()
459 {
460   return _abs_training_tolerance;
461 }
462 
get_Nmax()463 unsigned int RBEIMConstruction::get_Nmax() const
464 {
465   return _Nmax;
466 }
467 
set_Nmax(unsigned int Nmax)468 void RBEIMConstruction::set_Nmax(unsigned int Nmax)
469 {
470   _Nmax = Nmax;
471 }
472 
get_max_abs_value_in_training_set()473 Real RBEIMConstruction::get_max_abs_value_in_training_set() const
474 {
475   return _max_abs_value_in_training_set;
476 }
477 
store_eim_solutions_for_training_set()478 void RBEIMConstruction::store_eim_solutions_for_training_set()
479 {
480   LOG_SCOPE("store_eim_solutions_for_training_set()", "RBEIMConstruction");
481 
482   RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
483 
484   std::vector<DenseVector<Number>> & eim_solutions = get_rb_eim_evaluation().eim_solutions;
485   eim_solutions.clear();
486   eim_solutions.resize(get_n_training_samples());
487   for (auto i : make_range(get_n_training_samples()))
488     {
489       const auto & local_pf = _local_parametrized_functions_for_training[i];
490 
491       unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
492       if (RB_size > 0)
493         {
494           // Get the right-hand side vector for the EIM approximation
495           // by sampling the parametrized function (stored in solution)
496           // at the interpolation points.
497           DenseVector<Number> EIM_rhs(RB_size);
498           for (unsigned int j=0; j<RB_size; j++)
499             {
500               EIM_rhs(j) =
501                 RBEIMEvaluation::get_parametrized_function_value(comm(),
502                                                                  local_pf,
503                                                                  eim_eval.get_interpolation_points_elem_id(j),
504                                                                  eim_eval.get_interpolation_points_comp(j),
505                                                                  eim_eval.get_interpolation_points_qp(j));
506             }
507 
508           eim_eval.set_parameters( get_parameters() );
509           eim_eval.rb_eim_solve(EIM_rhs);
510           eim_solutions[i] = eim_eval.get_rb_eim_solution();
511         }
512     }
513 }
514 
compute_max_eim_error()515 std::pair<Real,unsigned int> RBEIMConstruction::compute_max_eim_error()
516 {
517   LOG_SCOPE("compute_max_eim_error()", "RBEIMConstruction");
518 
519   if (get_n_params() == 0)
520     {
521       // Just return 0 if we have no parameters.
522       return std::make_pair(0.,0);
523     }
524 
525   // keep track of the maximum error
526   unsigned int max_err_index = 0;
527   Real max_err = 0.;
528 
529   libmesh_error_msg_if(get_n_training_samples() != get_local_n_training_samples(),
530                        "Error: Training samples should be the same on all procs");
531 
532   for (auto i : make_range(get_n_training_samples()))
533     {
534       Real best_fit_error = compute_best_fit_error(i);
535 
536       if (best_fit_error > max_err)
537         {
538           max_err_index = i;
539           max_err = best_fit_error;
540         }
541     }
542 
543   return std::make_pair(max_err,max_err_index);
544 }
545 
initialize_parametrized_functions_in_training_set()546 void RBEIMConstruction::initialize_parametrized_functions_in_training_set()
547 {
548   LOG_SCOPE("initialize_parametrized_functions_in_training_set()", "RBEIMConstruction");
549 
550   libmesh_error_msg_if(!serial_training_set,
551                        "Error: We must have serial_training_set==true in "
552                        "RBEIMConstruction::initialize_parametrized_functions_in_training_set");
553 
554   libMesh::out << "Initializing parametrized functions in training set..." << std::endl;
555 
556   if (get_rb_eim_evaluation().get_parametrized_function().is_lookup_table)
557     get_rb_eim_evaluation().get_parametrized_function().initialize_lookup_table();
558 
559   // Store the locations of all quadrature points
560   initialize_qp_data();
561 
562   RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
563 
564   // Keep track of the largest value in our parametrized functions
565   // in the training set. We can use this value for normalization
566   // purposes, for example.
567   _max_abs_value_in_training_set = 0.;
568 
569   _local_parametrized_functions_for_training.resize( get_n_training_samples() );
570   for (auto i : make_range(get_n_training_samples()))
571     {
572       libMesh::out << "Initializing parametrized function for training sample "
573         << (i+1) << " of " << get_n_training_samples() << std::endl;
574 
575       set_params_from_training_set(i);
576 
577       eim_eval.get_parametrized_function().preevaluate_parametrized_function_on_mesh(get_parameters(),
578                                                                                      _local_quad_point_locations,
579                                                                                      _local_quad_point_subdomain_ids,
580                                                                                      _local_quad_point_locations_perturbations);
581 
582       unsigned int n_comps = eim_eval.get_parametrized_function().get_n_components();
583 
584       for (const auto & pr : _local_quad_point_locations)
585       {
586         dof_id_type elem_id = pr.first;
587         const auto & xyz_vector = pr.second;
588 
589         std::vector<std::vector<Number>> comps_and_qps(n_comps);
590         for (unsigned int comp=0; comp<n_comps; comp++)
591           {
592             comps_and_qps[comp].resize(xyz_vector.size());
593             for (unsigned int qp : index_range(xyz_vector))
594               {
595                 Number value =
596                   eim_eval.get_parametrized_function().lookup_preevaluated_value_on_mesh(comp, elem_id, qp);
597                 comps_and_qps[comp][qp] = value;
598 
599                 Real abs_value = std::abs(value);
600                 if (abs_value > _max_abs_value_in_training_set)
601                   _max_abs_value_in_training_set = abs_value;
602               }
603           }
604 
605         _local_parametrized_functions_for_training[i][elem_id] = comps_and_qps;
606       }
607     }
608 
609   libMesh::out << "Parametrized functions in training set initialized" << std::endl;
610 
611   comm().max(_max_abs_value_in_training_set);
612   libMesh::out << "Maximum absolute value in the training set: "
613     << _max_abs_value_in_training_set << std::endl << std::endl;
614 }
615 
initialize_qp_data()616 void RBEIMConstruction::initialize_qp_data()
617 {
618   LOG_SCOPE("initialize_qp_data()", "RBEIMConstruction");
619 
620   if (!get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
621     {
622       libMesh::out << "Initializing quadrature point locations" << std::endl;
623     }
624   else
625     {
626       libMesh::out << "Initializing quadrature point and perturbation locations" << std::endl;
627     }
628 
629   // Compute truth representation via L2 projection
630   const MeshBase & mesh = this->get_mesh();
631 
632   FEMContext context(*this);
633   init_context(context);
634 
635   FEBase * elem_fe = nullptr;
636   context.get_element_fe( 0, elem_fe );
637   const std::vector<Real> & JxW = elem_fe->get_JxW();
638   const std::vector<Point> & xyz = elem_fe->get_xyz();
639 
640   _local_quad_point_locations.clear();
641   _local_quad_point_subdomain_ids.clear();
642   _local_quad_point_JxW.clear();
643 
644   _local_quad_point_locations_perturbations.clear();
645 
646   for (const auto & elem : mesh.active_local_element_ptr_range())
647     {
648       dof_id_type elem_id = elem->id();
649 
650       context.pre_fe_reinit(*this, elem);
651       context.elem_fe_reinit();
652 
653       _local_quad_point_locations[elem_id] = xyz;
654       _local_quad_point_JxW[elem_id] = JxW;
655       _local_quad_point_subdomain_ids[elem_id] = elem->subdomain_id();
656 
657       if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
658         {
659           Real fd_delta = get_rb_eim_evaluation().get_parametrized_function().fd_delta;
660 
661           std::vector<std::vector<Point>> xyz_perturb_vec_at_qps;
662 
663           for (const Point & xyz_qp : xyz)
664             {
665               std::vector<Point> xyz_perturb_vec;
666               if (elem->dim() == 3)
667                 {
668                   Point xyz_perturb = xyz_qp;
669 
670                   xyz_perturb(0) += fd_delta;
671                   xyz_perturb_vec.emplace_back(xyz_perturb);
672                   xyz_perturb(0) -= fd_delta;
673 
674                   xyz_perturb(1) += fd_delta;
675                   xyz_perturb_vec.emplace_back(xyz_perturb);
676                   xyz_perturb(1) -= fd_delta;
677 
678                   xyz_perturb(2) += fd_delta;
679                   xyz_perturb_vec.emplace_back(xyz_perturb);
680                   xyz_perturb(2) -= fd_delta;
681                 }
682               else if (elem->dim() == 2)
683                 {
684                   // In this case we assume that we have a 2D element
685                   // embedded in 3D space. In this case we have to use
686                   // the following approach to perturb xyz:
687                   //  1) inverse map xyz to the reference element
688                   //  2) perturb on the reference element in the (xi,eta) "directions"
689                   //  3) map the perturbed points back to the physical element
690                   // This approach is necessary to ensure that the perturbed points
691                   // are still in the element.
692 
693                   Point xi_eta =
694                     FEMap::inverse_map(elem->dim(),
695                                       elem,
696                                       xyz_qp,
697                                       /*Newton iteration tolerance*/ TOLERANCE,
698                                       /*secure*/ true);
699 
700                   // Inverse map should map back to a 2D reference domain
701                   libmesh_assert(std::abs(xi_eta(2)) < TOLERANCE);
702 
703                   Point xi_eta_perturb = xi_eta;
704 
705                   xi_eta_perturb(0) += fd_delta;
706                   Point xyz_perturb_0 =
707                     FEMap::map(elem->dim(),
708                                elem,
709                                xi_eta_perturb);
710                   xi_eta_perturb(0) -= fd_delta;
711 
712                   xi_eta_perturb(1) += fd_delta;
713                   Point xyz_perturb_1 =
714                     FEMap::map(elem->dim(),
715                                elem,
716                                xi_eta_perturb);
717                   xi_eta_perturb(1) -= fd_delta;
718 
719                   // Finally, we rescale xyz_perturb_0 and xyz_perturb_1 so that
720                   // (xyz_perturb - xyz_qp).norm() == fd_delta, since this is
721                   // required in order to compute finite differences correctly.
722                   Point unit_0 = (xyz_perturb_0-xyz_qp).unit();
723                   Point unit_1 = (xyz_perturb_1-xyz_qp).unit();
724 
725                   xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_0);
726                   xyz_perturb_vec.emplace_back(xyz_qp + fd_delta*unit_1);
727                 }
728               else
729                 {
730                   // We current do nothing in the dim=1 case since
731                   // we have no need for this capability so far.
732                   // Support for this case could be added if it is
733                   // needed.
734                 }
735 
736               xyz_perturb_vec_at_qps.emplace_back(xyz_perturb_vec);
737             }
738 
739           _local_quad_point_locations_perturbations[elem_id] = xyz_perturb_vec_at_qps;
740         }
741     }
742 }
743 
744 Number
inner_product(const QpDataMap & v,const QpDataMap & w)745 RBEIMConstruction::inner_product(const QpDataMap & v, const QpDataMap & w)
746 {
747   LOG_SCOPE("inner_product()", "RBEIMConstruction");
748 
749   Number val = 0.;
750 
751   for (const auto & pr : v)
752     {
753       dof_id_type elem_id = pr.first;
754       const auto & v_comp_and_qp = pr.second;
755 
756       const auto & w_comp_and_qp = libmesh_map_find(w, elem_id);
757       const auto & JxW = libmesh_map_find(_local_quad_point_JxW, elem_id);
758 
759       for (const auto & comp : index_range(v_comp_and_qp))
760         {
761           const std::vector<Number> & v_qp = v_comp_and_qp[comp];
762           const std::vector<Number> & w_qp = w_comp_and_qp[comp];
763 
764           for (unsigned int qp : index_range(JxW))
765             val += JxW[qp] * v_qp[qp] * libmesh_conj(w_qp[qp]);
766         }
767     }
768 
769   comm().sum(val);
770   return val;
771 }
772 
get_max_abs_value(const QpDataMap & v)773 Real RBEIMConstruction::get_max_abs_value(const QpDataMap & v) const
774 {
775   LOG_SCOPE("get_max_abs_value()", "RBEIMConstruction");
776 
777   Real max_value = 0.;
778 
779   for (const auto & pr : v)
780     {
781       const auto & v_comp_and_qp = pr.second;
782 
783       for (const auto & comp : index_range(v_comp_and_qp))
784         {
785           const std::vector<Number> & v_qp = v_comp_and_qp[comp];
786           for (Number value : v_qp)
787             max_value = std::max(max_value, std::abs(value));
788         }
789     }
790 
791   comm().max(max_value);
792   return max_value;
793 }
794 
enrich_eim_approximation(unsigned int training_index)795 void RBEIMConstruction::enrich_eim_approximation(unsigned int training_index)
796 {
797   LOG_SCOPE("enrich_eim_approximation()", "RBEIMConstruction");
798 
799   RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
800 
801   set_params_from_training_set(training_index);
802 
803   // Make a copy of the parametrized function for training index, since we
804   // will modify this below to give us a new basis function.
805   auto local_pf = _local_parametrized_functions_for_training[training_index];
806 
807   // If we have at least one basis function, then we need to use
808   // rb_eim_solve() to find the EIM interpolation error. Otherwise,
809   // just use solution as is.
810   if (get_rb_eim_evaluation().get_n_basis_functions() > 0)
811     {
812       // Get the right-hand side vector for the EIM approximation
813       // by sampling the parametrized function (stored in solution)
814       // at the interpolation points.
815       unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
816       DenseVector<Number> EIM_rhs(RB_size);
817       for (unsigned int i=0; i<RB_size; i++)
818         {
819           EIM_rhs(i) =
820             RBEIMEvaluation::get_parametrized_function_value(comm(),
821                                                              local_pf,
822                                                              eim_eval.get_interpolation_points_elem_id(i),
823                                                              eim_eval.get_interpolation_points_comp(i),
824                                                              eim_eval.get_interpolation_points_qp(i));
825         }
826 
827       eim_eval.set_parameters( get_parameters() );
828       eim_eval.rb_eim_solve(EIM_rhs);
829 
830       // Load the "EIM residual" into solution by subtracting
831       // the EIM approximation
832       get_rb_eim_evaluation().decrement_vector(local_pf, eim_eval.get_rb_eim_solution());
833     }
834 
835   // Find the quadrature point at which local_pf (which now stores
836   // the "EIM residual") has maximum absolute value
837   Number optimal_value = 0.;
838   Point optimal_point;
839   unsigned int optimal_comp = 0;
840   dof_id_type optimal_elem_id = DofObject::invalid_id;
841   subdomain_id_type optimal_subdomain_id = 0;
842   unsigned int optimal_qp = 0;
843   std::vector<Point> optimal_point_perturbs;
844 
845   // Initialize largest_abs_value to be negative so that it definitely gets updated.
846   Real largest_abs_value = -1.;
847 
848   for (const auto & pr : local_pf)
849     {
850       dof_id_type elem_id = pr.first;
851       const auto & comp_and_qp = pr.second;
852 
853       for (const auto & comp : index_range(comp_and_qp))
854         {
855           const std::vector<Number> & qp_values = comp_and_qp[comp];
856 
857           for (unsigned int qp : index_range(qp_values))
858             {
859               Number value = qp_values[qp];
860               Real abs_value = std::abs(value);
861 
862               if (abs_value > largest_abs_value)
863                 {
864                   largest_abs_value = abs_value;
865                   optimal_value = value;
866                   optimal_comp = comp;
867                   optimal_elem_id = elem_id;
868                   optimal_qp = qp;
869 
870                   const auto & point_list =
871                     libmesh_map_find(_local_quad_point_locations, elem_id);
872 
873                   libmesh_error_msg_if(qp >= point_list.size(), "Error: Invalid qp");
874 
875                   optimal_point = point_list[qp];
876 
877                   optimal_subdomain_id = libmesh_map_find(_local_quad_point_subdomain_ids, elem_id);
878 
879                   if (get_rb_eim_evaluation().get_parametrized_function().requires_xyz_perturbations)
880                     {
881                       const auto & perturb_list =
882                         libmesh_map_find(_local_quad_point_locations_perturbations, elem_id);
883 
884                       libmesh_error_msg_if(qp >= perturb_list.size(), "Error: Invalid qp");
885 
886                       optimal_point_perturbs = perturb_list[qp];
887                     }
888                 }
889             }
890         }
891     }
892 
893   // Find out which processor has the largest of the abs values
894   // and broadcast from that processor.
895   unsigned int proc_ID_index;
896   this->comm().maxloc(largest_abs_value, proc_ID_index);
897 
898   this->comm().broadcast(optimal_value, proc_ID_index);
899   this->comm().broadcast(optimal_point, proc_ID_index);
900   this->comm().broadcast(optimal_comp, proc_ID_index);
901   this->comm().broadcast(optimal_elem_id, proc_ID_index);
902   this->comm().broadcast(optimal_subdomain_id, proc_ID_index);
903   this->comm().broadcast(optimal_qp, proc_ID_index);
904   this->comm().broadcast(optimal_point_perturbs, proc_ID_index);
905 
906   libmesh_error_msg_if(optimal_elem_id == DofObject::invalid_id, "Error: Invalid element ID");
907 
908   // Scale local_pf so that its largest value is 1.0
909   scale_parametrized_function(local_pf, 1./optimal_value);
910 
911   // Add local_pf as the new basis function and store data
912   // associated with the interpolation point.
913   eim_eval.add_basis_function_and_interpolation_data(local_pf,
914                                                      optimal_point,
915                                                      optimal_comp,
916                                                      optimal_elem_id,
917                                                      optimal_subdomain_id,
918                                                      optimal_qp,
919                                                      optimal_point_perturbs);
920 }
921 
update_eim_matrices()922 void RBEIMConstruction::update_eim_matrices()
923 {
924   LOG_SCOPE("update_eim_matrices()", "RBEIMConstruction");
925 
926   RBEIMEvaluation & eim_eval = get_rb_eim_evaluation();
927   unsigned int RB_size = eim_eval.get_n_basis_functions();
928 
929   libmesh_assert_msg(RB_size >= 1, "Must have at least 1 basis function.");
930 
931   // update the matrix that is used to evaluate L2 projections
932   // into the EIM approximation space
933   for (unsigned int i=(RB_size-1); i<RB_size; i++)
934     {
935       for (unsigned int j=0; j<RB_size; j++)
936         {
937           Number value = inner_product(eim_eval.get_basis_function(j),
938                                        eim_eval.get_basis_function(i));
939 
940           _eim_projection_matrix(i,j) = value;
941           if (i!=j)
942             {
943               // The inner product matrix is assumed to be hermitian
944               _eim_projection_matrix(j,i) = libmesh_conj(value);
945             }
946         }
947     }
948 
949   // update the EIM interpolation matrix
950   for (unsigned int j=0; j<RB_size; j++)
951     {
952       // Evaluate the basis functions at the new interpolation point in order
953       // to update the interpolation matrix
954       Number value =
955         eim_eval.get_eim_basis_function_value(j,
956                                               eim_eval.get_interpolation_points_elem_id(RB_size-1),
957                                               eim_eval.get_interpolation_points_comp(RB_size-1),
958                                               eim_eval.get_interpolation_points_qp(RB_size-1));
959       eim_eval.set_interpolation_matrix_entry(RB_size-1, j, value);
960 
961     }
962 }
963 
compute_best_fit_error(unsigned int training_index)964 Real RBEIMConstruction::compute_best_fit_error(unsigned int training_index)
965 {
966   LOG_SCOPE("compute_best_fit_error()", "RBEIMConstruction");
967 
968   set_params_from_training_set(training_index);
969 
970   // Make a copy of the pre-computed solution for the specified training sample
971   // since we will modify it below to compute the best fit error.
972   QpDataMap solution_copy = _local_parametrized_functions_for_training[training_index];
973 
974   const unsigned int RB_size = get_rb_eim_evaluation().get_n_basis_functions();
975   DenseVector<Number> best_fit_coeffs;
976 
977   switch(best_fit_type_flag)
978     {
979     case(PROJECTION_BEST_FIT):
980       {
981         // Perform an L2 projection in order to find the best approximation to
982         // the parametrized function from the current EIM space.
983         DenseVector<Number> best_fit_rhs(RB_size);
984         for (unsigned int i=0; i<RB_size; i++)
985           {
986             best_fit_rhs(i) = inner_product(solution_copy, get_rb_eim_evaluation().get_basis_function(i));
987           }
988 
989         // Now compute the best fit by an LU solve
990         DenseMatrix<Number> RB_inner_product_matrix_N(RB_size);
991         _eim_projection_matrix.get_principal_submatrix(RB_size, RB_inner_product_matrix_N);
992 
993         RB_inner_product_matrix_N.lu_solve(best_fit_rhs, best_fit_coeffs);
994         break;
995       }
996     case(EIM_BEST_FIT):
997       {
998         // Perform EIM solve in order to find the approximation to solution
999         // (rb_eim_solve provides the EIM basis function coefficients used below)
1000 
1001         // Turn off error estimation for this rb_eim_solve, we use the linfty norm instead
1002         get_rb_eim_evaluation().evaluate_eim_error_bound = false;
1003         get_rb_eim_evaluation().set_parameters( get_parameters() );
1004         get_rb_eim_evaluation().rb_eim_solve(RB_size);
1005         get_rb_eim_evaluation().evaluate_eim_error_bound = true;
1006 
1007         best_fit_coeffs = get_rb_eim_evaluation().get_rb_eim_solution();
1008         break;
1009       }
1010     default:
1011       libmesh_error_msg("Should not reach here");
1012     }
1013 
1014   get_rb_eim_evaluation().decrement_vector(solution_copy, best_fit_coeffs);
1015 
1016   Real best_fit_error = get_max_abs_value(solution_copy);
1017   return best_fit_error;
1018 }
1019 
scale_parametrized_function(QpDataMap & local_pf,Number scaling_factor)1020 void RBEIMConstruction::scale_parametrized_function(
1021     QpDataMap & local_pf,
1022     Number scaling_factor)
1023 {
1024   LOG_SCOPE("scale_parametrized_function()", "RBEIMConstruction");
1025 
1026   for (auto & pr : local_pf)
1027     {
1028       auto & comp_and_qp = pr.second;
1029 
1030       for (unsigned int comp : index_range(comp_and_qp))
1031         {
1032           std::vector<Number> & qp_values = comp_and_qp[comp];
1033 
1034           for (unsigned int qp : index_range(qp_values))
1035             {
1036               qp_values[qp] *= scaling_factor;
1037             }
1038         }
1039     }
1040 }
1041 
1042 } // namespace libMesh
1043