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