LCOV - code coverage report
Current view: top level - src/reduced_basis - rb_construction.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 690 1225 56.3 %
Date: 2025-08-19 19:27:09 Functions: 70 106 66.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // rbOOmit: An implementation of the Certified Reduced Basis method.
       2             : // Copyright (C) 2009, 2010 David J. Knezevic
       3             : 
       4             : // This file is part of rbOOmit.
       5             : 
       6             : // rbOOmit is free software; you can redistribute it and/or
       7             : // modify it under the terms of the GNU Lesser General Public
       8             : // License as published by the Free Software Foundation; either
       9             : // version 2.1 of the License, or (at your option) any later version.
      10             : 
      11             : // rbOOmit is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14             : // Lesser General Public License for more details.
      15             : 
      16             : // You should have received a copy of the GNU Lesser General Public
      17             : // License along with this library; if not, write to the Free Software
      18             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      19             : 
      20             : // rbOOmit includes
      21             : #include "libmesh/rb_construction_base.h"
      22             : #include "libmesh/rb_construction.h"
      23             : #include "libmesh/rb_assembly_expansion.h"
      24             : #include "libmesh/rb_evaluation.h"
      25             : #include "libmesh/elem_assembly.h"
      26             : 
      27             : // LibMesh includes
      28             : #include "libmesh/numeric_vector.h"
      29             : #include "libmesh/sparse_matrix.h"
      30             : #include "libmesh/dof_map.h"
      31             : #include "libmesh/libmesh_logging.h"
      32             : #include "libmesh/equation_systems.h"
      33             : #include "libmesh/exodusII_io.h"
      34             : #include "libmesh/gmv_io.h"
      35             : #include "libmesh/linear_solver.h"
      36             : #include "libmesh/getpot.h"
      37             : #include "libmesh/int_range.h"
      38             : #include "libmesh/mesh_base.h"
      39             : #include "libmesh/parallel.h"
      40             : #include "libmesh/xdr_cxx.h"
      41             : #include "libmesh/timestamp.h"
      42             : #include "libmesh/petsc_linear_solver.h"
      43             : #include "libmesh/dg_fem_context.h"
      44             : #include "libmesh/dirichlet_boundaries.h"
      45             : #include "libmesh/zero_function.h"
      46             : #include "libmesh/coupling_matrix.h"
      47             : #include "libmesh/face_tri3_subdivision.h"
      48             : #include "libmesh/quadrature.h"
      49             : #include "libmesh/utility.h"
      50             : 
      51             : // C++ includes
      52             : #include <sys/types.h>
      53             : #include <sys/stat.h>
      54             : #include <errno.h>
      55             : #include <fstream>
      56             : #include <sstream>
      57             : #include <limits>
      58             : #include <stdlib.h> // mkstemps on Linux
      59             : #ifdef LIBMESH_HAVE_UNISTD_H
      60             : #include <unistd.h> // mkstemps on MacOS
      61             : #endif
      62             : 
      63             : namespace libMesh
      64             : {
      65             : 
      66         568 : RBConstruction::RBConstruction (EquationSystems & es,
      67             :                                 const std::string & name_in,
      68         568 :                                 const unsigned int number_in)
      69             :   : Parent(es, name_in, number_in),
      70         536 :     inner_product_solver(LinearSolver<Number>::build(es.comm())),
      71         536 :     extra_linear_solver(nullptr),
      72         536 :     inner_product_matrix(SparseMatrix<Number>::build(es.comm())),
      73         536 :     skip_residual_in_train_reduced_basis(false),
      74         536 :     exit_on_repeated_greedy_parameters(true),
      75         536 :     impose_internal_fluxes(false),
      76         536 :     skip_degenerate_sides(true),
      77         536 :     compute_RB_inner_product(false),
      78         536 :     store_non_dirichlet_operators(false),
      79         536 :     store_untransformed_basis(false),
      80         536 :     use_empty_rb_solve_in_greedy(true),
      81         536 :     Fq_representor_innerprods_computed(false),
      82         536 :     Nmax(0),
      83         536 :     delta_N(1),
      84         536 :     output_dual_innerprods_computed(false),
      85         536 :     assert_convergence(true),
      86         536 :     rb_eval(nullptr),
      87         536 :     inner_product_assembly(nullptr),
      88         536 :     use_energy_inner_product(false),
      89         536 :     rel_training_tolerance(1.e-4),
      90         536 :     abs_training_tolerance(1.e-12),
      91         536 :     normalize_rb_bound_in_greedy(false),
      92         536 :     RB_training_type("Greedy"),
      93         536 :     _preevaluate_thetas_flag(false),
      94        1104 :     _preevaluate_thetas_completed(false)
      95             : {
      96             :   // set assemble_before_solve flag to false
      97             :   // so that we control matrix assembly.
      98         568 :   assemble_before_solve = false;
      99         568 : }
     100             : 
     101        2144 : RBConstruction::~RBConstruction () = default;
     102             : 
     103           0 : void RBConstruction::clear()
     104             : {
     105           0 :   LOG_SCOPE("clear()", "RBConstruction");
     106             : 
     107           0 :   Parent::clear();
     108             : 
     109           0 :   Aq_vector.clear();
     110           0 :   Fq_vector.clear();
     111           0 :   outputs_vector.clear();
     112             : 
     113           0 :   if (store_non_dirichlet_operators)
     114             :     {
     115           0 :       non_dirichlet_Aq_vector.clear();
     116           0 :       non_dirichlet_Fq_vector.clear();
     117           0 :       non_dirichlet_outputs_vector.clear();
     118             :     }
     119             : 
     120             :   // Also delete the Fq representors
     121           0 :   Fq_representor.clear();
     122             : 
     123             :   // Set Fq_representor_innerprods_computed flag to false now
     124             :   // that we've cleared the Fq representors
     125           0 :   Fq_representor_innerprods_computed = false;
     126           0 : }
     127             : 
     128         568 : std::string RBConstruction::system_type () const
     129             : {
     130         568 :   return "RBConstruction";
     131             : }
     132             : 
     133      160467 : void RBConstruction::solve_for_matrix_and_rhs(LinearSolver<Number> & input_solver,
     134             :                                               SparseMatrix<Number> & input_matrix,
     135             :                                               NumericVector<Number> & input_rhs)
     136             : {
     137             :   // This is similar to LinearImplicitSysmte::solve()
     138             : 
     139             :   // Get a reference to the EquationSystems
     140             :   const EquationSystems & es =
     141        9156 :     this->get_equation_systems();
     142             : 
     143             :   // If the linear solver hasn't been initialized, we do so here.
     144      160467 :   input_solver.init();
     145             : 
     146             :   // Get the user-specifiied linear solver tolerance
     147             :   const double tol  =
     148      160467 :     double(es.parameters.get<Real>("linear solver tolerance"));
     149             : 
     150             :   // Get the user-specified maximum # of linear solver iterations
     151             :   const unsigned int maxits =
     152      160467 :     es.parameters.get<unsigned int>("linear solver maximum iterations");
     153             : 
     154             :   // It's good practice to clear the solution vector first since it can
     155             :   // affect convergence of iterative solvers
     156      160467 :   solution->zero();
     157             : 
     158             :   // Solve the linear system.
     159             :   // Store the number of linear iterations required to
     160             :   // solve and the final residual.
     161      160467 :   std::tie(_n_linear_iterations, _final_linear_residual) =
     162      169623 :     input_solver.solve (input_matrix, *solution, input_rhs, tol, maxits);
     163             : 
     164      160467 :   get_dof_map().enforce_constraints_exactly(*this);
     165             : 
     166             :   // Update the system after the solve
     167      160467 :   this->update();
     168      160467 : }
     169             : 
     170         568 : void RBConstruction::set_rb_evaluation(RBEvaluation & rb_eval_in)
     171             : {
     172         568 :   rb_eval = &rb_eval_in;
     173         568 : }
     174             : 
     175    30451994 : RBEvaluation & RBConstruction::get_rb_evaluation()
     176             : {
     177    30451994 :   libmesh_error_msg_if(!rb_eval, "Error: RBEvaluation object hasn't been initialized yet");
     178             : 
     179    30451994 :   return *rb_eval;
     180             : }
     181             : 
     182       90453 : const RBEvaluation & RBConstruction::get_rb_evaluation() const
     183             : {
     184       90453 :   libmesh_error_msg_if(!rb_eval, "Error: RBEvaluation object hasn't been initialized yet");
     185             : 
     186       90453 :   return *rb_eval;
     187             : }
     188             : 
     189         354 : bool RBConstruction::is_rb_eval_initialized() const
     190             : {
     191         354 :   return (rb_eval != nullptr);
     192             : }
     193             : 
     194     6111903 : RBThetaExpansion & RBConstruction::get_rb_theta_expansion()
     195             : {
     196     6111903 :   return get_rb_evaluation().get_rb_theta_expansion();
     197             : }
     198             : 
     199        2342 : const RBThetaExpansion & RBConstruction::get_rb_theta_expansion() const
     200             : {
     201        2342 :   return get_rb_evaluation().get_rb_theta_expansion();
     202             : }
     203             : 
     204         284 : void RBConstruction::process_parameters_file (const std::string & parameters_filename)
     205             : {
     206             :   // First read in data from input_filename
     207         584 :   GetPot infile(parameters_filename);
     208             : 
     209         284 :   const unsigned int n_training_samples = infile("n_training_samples",0);
     210         284 :   const bool deterministic_training = infile("deterministic_training",false);
     211         284 :   unsigned int training_parameters_random_seed_in =
     212             :     static_cast<unsigned int>(-1);
     213         284 :   training_parameters_random_seed_in = infile("training_parameters_random_seed",
     214             :                                               training_parameters_random_seed_in);
     215         284 :   const bool quiet_mode_in = infile("quiet_mode", quiet_mode);
     216         284 :   const unsigned int Nmax_in = infile("Nmax", Nmax);
     217         292 :   const Real rel_training_tolerance_in = infile("rel_training_tolerance",
     218         284 :                                                 rel_training_tolerance);
     219         292 :   const Real abs_training_tolerance_in = infile("abs_training_tolerance",
     220         284 :                                                 abs_training_tolerance);
     221             : 
     222             :   // Initialize value to false, let the input file value override.
     223         292 :   const bool normalize_rb_bound_in_greedy_in = infile("normalize_rb_bound_in_greedy",
     224         284 :                                                       false);
     225             : 
     226         292 :   const std::string RB_training_type_in = infile("RB_training_type", "Greedy");
     227             : 
     228             :   // Read in the parameters from the input file too
     229         276 :   unsigned int n_continuous_parameters = infile.vector_variable_size("parameter_names");
     230         300 :   RBParameters mu_min_in;
     231         300 :   RBParameters mu_max_in;
     232        1563 :   for (unsigned int i=0; i<n_continuous_parameters; i++)
     233             :     {
     234             :       // Read in the parameter names
     235        1315 :       std::string param_name = infile("parameter_names", "NONE", i);
     236             : 
     237             :       {
     238        1279 :         Real min_val = infile(param_name, 0., 0);
     239        1279 :         mu_min_in.set_value(param_name, min_val);
     240             :       }
     241             : 
     242             :       {
     243        1279 :         Real max_val = infile(param_name, 0., 1);
     244        1279 :         mu_max_in.set_value(param_name, max_val);
     245             :       }
     246             :     }
     247             : 
     248          16 :   std::map<std::string, std::vector<Real>> discrete_parameter_values_in;
     249             : 
     250         276 :   unsigned int n_discrete_parameters = infile.vector_variable_size("discrete_parameter_names");
     251         284 :   for (unsigned int i=0; i<n_discrete_parameters; i++)
     252             :     {
     253           0 :       std::string param_name = infile("discrete_parameter_names", "NONE", i);
     254             : 
     255           0 :       unsigned int n_vals_for_param = infile.vector_variable_size(param_name);
     256           0 :       std::vector<Real> vals_for_param(n_vals_for_param);
     257           0 :       for (auto j : make_range(vals_for_param.size()))
     258           0 :         vals_for_param[j] = infile(param_name, 0., j);
     259             : 
     260           0 :       discrete_parameter_values_in[param_name] = vals_for_param;
     261             :     }
     262             : 
     263          16 :   std::map<std::string,bool> log_scaling_in;
     264             :   // For now, just set all entries to false.
     265             :   // TODO: Implement a decent way to specify log-scaling true/false
     266             :   // in the input text file
     267        1563 :   for (const auto & pr : mu_min_in)
     268        1279 :     log_scaling_in[pr.first] = false;
     269             : 
     270             :   // Set the parameters that have been read in
     271         284 :   set_rb_construction_parameters(n_training_samples,
     272             :                                  deterministic_training,
     273             :                                  static_cast<int>(training_parameters_random_seed_in),
     274             :                                  quiet_mode_in,
     275             :                                  Nmax_in,
     276             :                                  rel_training_tolerance_in,
     277             :                                  abs_training_tolerance_in,
     278             :                                  normalize_rb_bound_in_greedy_in,
     279             :                                  RB_training_type_in,
     280             :                                  mu_min_in,
     281             :                                  mu_max_in,
     282             :                                  discrete_parameter_values_in,
     283             :                                  log_scaling_in);
     284         552 : }
     285             : 
     286         284 : void RBConstruction::set_rb_construction_parameters(
     287             :                                                     unsigned int n_training_samples_in,
     288             :                                                     bool deterministic_training_in,
     289             :                                                     int training_parameters_random_seed_in,
     290             :                                                     bool quiet_mode_in,
     291             :                                                     unsigned int Nmax_in,
     292             :                                                     Real rel_training_tolerance_in,
     293             :                                                     Real abs_training_tolerance_in,
     294             :                                                     bool normalize_rb_bound_in_greedy_in,
     295             :                                                     const std::string & RB_training_type_in,
     296             :                                                     const RBParameters & mu_min_in,
     297             :                                                     const RBParameters & mu_max_in,
     298             :                                                     const std::map<std::string, std::vector<Real>> & discrete_parameter_values_in,
     299             :                                                     const std::map<std::string,bool> & log_scaling_in,
     300             :                                                     std::map<std::string, std::vector<RBParameter>> * training_sample_list)
     301             : {
     302             :   // Read in training_parameters_random_seed value.  This is used to
     303             :   // seed the RNG when picking the training parameters.  By default the
     304             :   // value is -1, which means use std::time to seed the RNG.
     305         284 :   set_training_random_seed(training_parameters_random_seed_in);
     306             : 
     307             :   // Set quiet mode
     308           8 :   set_quiet_mode(quiet_mode_in);
     309             : 
     310             :   // Initialize RB parameters
     311         284 :   set_Nmax(Nmax_in);
     312             : 
     313           8 :   set_rel_training_tolerance(rel_training_tolerance_in);
     314           8 :   set_abs_training_tolerance(abs_training_tolerance_in);
     315             : 
     316           8 :   set_normalize_rb_bound_in_greedy(normalize_rb_bound_in_greedy_in);
     317             : 
     318         284 :   set_RB_training_type(RB_training_type_in);
     319             : 
     320             :   // Initialize the parameter ranges and the parameters themselves
     321         284 :   initialize_parameters(mu_min_in, mu_max_in, discrete_parameter_values_in);
     322             : 
     323           8 :   bool updated_deterministic_training = deterministic_training_in;
     324         284 :   if (training_sample_list && (this->get_parameters_min().n_parameters() > 3))
     325             :     {
     326             :       // In this case we force deterministic_training to be false because
     327             :       // a) deterministic training samples are not currrently supported with
     328             :       //    more than 3 parameters, and
     329             :       // b) we will overwrite the training samples anyway in the call to
     330             :       //    load_training_set() below, so we do not want to generate an
     331             :       //    error due to deterministic training sample generation when
     332             :       //    the samples will be overwritten anyway.
     333           0 :       updated_deterministic_training = false;
     334             :     }
     335             : 
     336         284 :   initialize_training_parameters(this->get_parameters_min(),
     337             :                                  this->get_parameters_max(),
     338             :                                  n_training_samples_in,
     339             :                                  log_scaling_in,
     340          16 :                                  updated_deterministic_training); // use deterministic parameters
     341             : 
     342         284 :   if (training_sample_list)
     343             :     {
     344             :       // Note that we must call initialize_training_parameters() before
     345             :       // load_training_set() in order to initialize the parameter vectors.
     346           0 :       load_training_set(*training_sample_list);
     347             :     }
     348         284 : }
     349             : 
     350         284 : void RBConstruction::print_info() const
     351             : {
     352             :   // Print out info that describes the current setup
     353           8 :   libMesh::out << std::endl << "RBConstruction parameters:" << std::endl;
     354           8 :   libMesh::out << "system name: " << this->name() << std::endl;
     355          16 :   libMesh::out << "Nmax: " << Nmax << std::endl;
     356          16 :   libMesh::out << "Greedy relative error tolerance: " << get_rel_training_tolerance() << std::endl;
     357          16 :   libMesh::out << "Greedy absolute error tolerance: " << get_abs_training_tolerance() << std::endl;
     358          16 :   libMesh::out << "Do we normalize RB error bound in greedy? " << get_normalize_rb_bound_in_greedy() << std::endl;
     359         284 :   libMesh::out << "RB training type: " << get_RB_training_type() << std::endl;
     360         284 :   if (is_rb_eval_initialized())
     361             :     {
     362         284 :       libMesh::out << "Aq operators attached: " << get_rb_theta_expansion().get_n_A_terms() << std::endl;
     363         284 :       libMesh::out << "Fq functions attached: " << get_rb_theta_expansion().get_n_F_terms() << std::endl;
     364         284 :       libMesh::out << "n_outputs: " << get_rb_theta_expansion().get_n_outputs() << std::endl;
     365         852 :       for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
     366         568 :         libMesh::out << "output " << n << ", Q_l = " << get_rb_theta_expansion().get_n_output_terms(n) << std::endl;
     367             :     }
     368             :   else
     369             :     {
     370           0 :       libMesh::out << "RBThetaExpansion member is not set yet" << std::endl;
     371             :     }
     372         284 :   libMesh::out << "Number of parameters: " << get_n_params() << std::endl;
     373        1563 :   for (const auto & pr : get_parameters())
     374        1279 :     if (!is_discrete_parameter(pr.first))
     375             :       {
     376          36 :         libMesh::out <<   "Parameter " << pr.first
     377        1279 :                      << ": Min = " << get_parameter_min(pr.first)
     378        1279 :                      << ", Max = " << get_parameter_max(pr.first) << std::endl;
     379             :       }
     380             : 
     381         284 :   print_discrete_parameter_values();
     382         284 :   libMesh::out << "n_training_samples: " << get_n_training_samples() << std::endl;
     383          16 :   libMesh::out << "quiet mode? " << is_quiet() << std::endl;
     384           8 :   libMesh::out << std::endl;
     385         284 : }
     386             : 
     387          71 : void RBConstruction::print_basis_function_orthogonality() const
     388             : {
     389          73 :   std::unique_ptr<NumericVector<Number>> temp = solution->clone();
     390             : 
     391        1491 :   for (unsigned int i=0; i<get_rb_evaluation().get_n_basis_functions(); i++)
     392             :     {
     393       29820 :       for (unsigned int j=0; j<get_rb_evaluation().get_n_basis_functions(); j++)
     394             :         {
     395       28400 :           get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*temp, get_rb_evaluation().get_basis_function(j));
     396       28400 :           Number value = temp->dot( get_rb_evaluation().get_basis_function(i) );
     397             : 
     398         800 :           libMesh::out << value << " ";
     399             :         }
     400          40 :       libMesh::out << std::endl;
     401             :     }
     402           2 :   libMesh::out << std::endl;
     403          71 : }
     404             : 
     405         568 : void RBConstruction::set_rb_assembly_expansion(RBAssemblyExpansion & rb_assembly_expansion_in)
     406             : {
     407         568 :   rb_assembly_expansion = &rb_assembly_expansion_in;
     408         568 : }
     409             : 
     410         112 : RBAssemblyExpansion & RBConstruction::get_rb_assembly_expansion()
     411             : {
     412         112 :   libmesh_error_msg_if(!rb_assembly_expansion, "Error: RBAssemblyExpansion object hasn't been initialized yet");
     413             : 
     414         112 :   return *rb_assembly_expansion;
     415             : }
     416             : 
     417         568 : void RBConstruction::set_inner_product_assembly(ElemAssembly & inner_product_assembly_in)
     418             : {
     419         568 :   use_energy_inner_product = false;
     420         568 :   inner_product_assembly = &inner_product_assembly_in;
     421         568 : }
     422             : 
     423           0 : ElemAssembly & RBConstruction::get_inner_product_assembly()
     424             : {
     425           0 :   libmesh_error_msg_if(use_energy_inner_product,
     426             :                        "Error: inner_product_assembly not available since we're using energy inner-product");
     427             : 
     428           0 :   libmesh_error_msg_if(!inner_product_assembly,
     429             :                        "Error: inner_product_assembly hasn't been initialized yet");
     430             : 
     431           0 :   return *inner_product_assembly;
     432             : }
     433             : 
     434           0 : void RBConstruction::set_energy_inner_product(const std::vector<Number> & energy_inner_product_coeffs_in)
     435             : {
     436           0 :   use_energy_inner_product = true;
     437           0 :   energy_inner_product_coeffs = energy_inner_product_coeffs_in;
     438           0 : }
     439             : 
     440           0 : void RBConstruction::zero_constrained_dofs_on_vector(NumericVector<Number> & vector) const
     441             : {
     442             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     443           0 :   const DofMap & dof_map = get_dof_map();
     444             : 
     445           0 :   for (dof_id_type i=dof_map.first_dof(); i<dof_map.end_dof(); i++)
     446             :     {
     447           0 :       if (get_dof_map().is_constrained_dof(i))
     448             :         {
     449           0 :           vector.set(i, 0.);
     450             :         }
     451             :     }
     452             : #endif
     453             : 
     454           0 :   vector.close();
     455           0 : }
     456             : 
     457        4956 : bool RBConstruction::check_if_zero_truth_solve() const
     458             : {
     459        4956 :   return (solution->l2_norm() == 0.);
     460             : }
     461             : 
     462         284 : void RBConstruction::initialize_rb_construction(bool skip_matrix_assembly,
     463             :                                                 bool skip_vector_assembly)
     464             : {
     465         284 :   if (!skip_matrix_assembly && !skip_vector_assembly)
     466             :     {
     467             :       // Check that the theta and assembly objects are consistently sized
     468           8 :       libmesh_assert_equal_to (get_rb_theta_expansion().get_n_A_terms(), get_rb_assembly_expansion().get_n_A_terms());
     469           8 :       libmesh_assert_equal_to (get_rb_theta_expansion().get_n_F_terms(), get_rb_assembly_expansion().get_n_F_terms());
     470           8 :       libmesh_assert_equal_to (get_rb_theta_expansion().get_n_outputs(), get_rb_assembly_expansion().get_n_outputs());
     471         852 :       for (unsigned int i=0; i<get_rb_theta_expansion().get_n_outputs(); i++)
     472             :         {
     473          16 :           libmesh_assert_equal_to (get_rb_theta_expansion().get_n_output_terms(i),
     474             :                                    get_rb_assembly_expansion().get_n_output_terms(i));
     475             :         }
     476             :     }
     477             : 
     478             :   // Perform the initialization
     479         284 :   allocate_data_structures();
     480         284 :   assemble_affine_expansion(skip_matrix_assembly, skip_vector_assembly);
     481             : 
     482             :   // inner_product_solver performs solves with the same matrix every time
     483             :   // hence we can set reuse_preconditioner(true).
     484         284 :   inner_product_solver->reuse_preconditioner(true);
     485             : 
     486             :   // The primary solver is used for truth solves and other solves that
     487             :   // require different matrices, so set reuse_preconditioner(false).
     488         284 :   get_linear_solver()->reuse_preconditioner(false);
     489             : 
     490         284 : }
     491             : 
     492         284 : void RBConstruction::assemble_affine_expansion(bool skip_matrix_assembly,
     493             :                                                bool skip_vector_assembly)
     494             : {
     495         284 :   if (!skip_matrix_assembly)
     496             :     {
     497             :       // Assemble and store all of the matrices
     498         284 :       this->assemble_misc_matrices();
     499         284 :       this->assemble_all_affine_operators();
     500             :     }
     501             : 
     502         284 :   if (!skip_vector_assembly)
     503             :     {
     504             :       // Assemble and store all of the vectors
     505         284 :       this->assemble_all_affine_vectors();
     506         284 :       this->assemble_all_output_vectors();
     507             :     }
     508         284 : }
     509             : 
     510         284 : void RBConstruction::allocate_data_structures()
     511             : {
     512             :   // Resize vectors for storing mesh-dependent data
     513         284 :   Aq_vector.resize(get_rb_theta_expansion().get_n_A_terms());
     514         284 :   Fq_vector.resize(get_rb_theta_expansion().get_n_F_terms());
     515             : 
     516             :   // Resize the Fq_representors and initialize each to nullptr.
     517             :   // These are basis independent and hence stored here, whereas
     518             :   // the Aq_representors are stored in RBEvaluation
     519         284 :   Fq_representor.resize(get_rb_theta_expansion().get_n_F_terms());
     520             : 
     521             :   // Initialize vectors for the inner products of the Fq representors
     522             :   // These are basis independent and therefore stored here.
     523         284 :   unsigned int Q_f_hat = get_rb_theta_expansion().get_n_F_terms()*(get_rb_theta_expansion().get_n_F_terms()+1)/2;
     524         284 :   Fq_representor_innerprods.resize(Q_f_hat);
     525             : 
     526             :   // Resize the output vectors
     527         284 :   outputs_vector.resize(get_rb_theta_expansion().get_n_outputs());
     528         852 :   for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
     529         584 :     outputs_vector[n].resize( get_rb_theta_expansion().get_n_output_terms(n) );
     530             : 
     531             :   // Resize the output dual norm vectors
     532         284 :   output_dual_innerprods.resize(get_rb_theta_expansion().get_n_outputs());
     533         852 :   for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
     534             :     {
     535         568 :       unsigned int Q_l_hat = get_rb_theta_expansion().get_n_output_terms(n)*(get_rb_theta_expansion().get_n_output_terms(n)+1)/2;
     536         584 :       output_dual_innerprods[n].resize(Q_l_hat);
     537             :     }
     538             : 
     539             :   {
     540           8 :     DofMap & dof_map = this->get_dof_map();
     541             : 
     542         284 :     dof_map.attach_matrix(*inner_product_matrix);
     543         284 :     inner_product_matrix->init();
     544         284 :     inner_product_matrix->zero();
     545             : 
     546         284 :     if(store_non_dirichlet_operators)
     547             :       {
     548             :         // We also need a non-Dirichlet inner-product matrix
     549           0 :         non_dirichlet_inner_product_matrix = SparseMatrix<Number>::build(this->comm());
     550           0 :         dof_map.attach_matrix(*non_dirichlet_inner_product_matrix);
     551           0 :         non_dirichlet_inner_product_matrix->init();
     552           0 :         non_dirichlet_inner_product_matrix->zero();
     553             :       }
     554             : 
     555        1136 :     for (unsigned int q=0; q<get_rb_theta_expansion().get_n_A_terms(); q++)
     556             :       {
     557             :         // Initialize the memory for the matrices
     558         852 :         Aq_vector[q] = SparseMatrix<Number>::build(this->comm());
     559         876 :         dof_map.attach_matrix(*Aq_vector[q]);
     560         876 :         Aq_vector[q]->init();
     561         876 :         Aq_vector[q]->zero();
     562             :       }
     563             : 
     564             :     // We also need to initialize a second set of non-Dirichlet operators
     565         284 :     if (store_non_dirichlet_operators)
     566             :       {
     567           0 :         non_dirichlet_Aq_vector.resize(get_rb_theta_expansion().get_n_A_terms());
     568           0 :         for (unsigned int q=0; q<get_rb_theta_expansion().get_n_A_terms(); q++)
     569             :           {
     570             :             // Initialize the memory for the matrices
     571           0 :             non_dirichlet_Aq_vector[q] = SparseMatrix<Number>::build(this->comm());
     572           0 :             dof_map.attach_matrix(*non_dirichlet_Aq_vector[q]);
     573           0 :             non_dirichlet_Aq_vector[q]->init();
     574           0 :             non_dirichlet_Aq_vector[q]->zero();
     575             :           }
     576             :       }
     577             :   }
     578             : 
     579             :   // Initialize the vectors
     580        1278 :   for (unsigned int q=0; q<get_rb_theta_expansion().get_n_F_terms(); q++)
     581             :     {
     582             :       // Initialize the memory for the vectors
     583         994 :       Fq_vector[q] = NumericVector<Number>::build(this->comm());
     584        1022 :       Fq_vector[q]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     585             :     }
     586             : 
     587             :   // We also need to initialize a second set of non-Dirichlet operators
     588         284 :   if (store_non_dirichlet_operators)
     589             :     {
     590           0 :       non_dirichlet_Fq_vector.resize(get_rb_theta_expansion().get_n_F_terms());
     591           0 :       for (unsigned int q=0; q<get_rb_theta_expansion().get_n_F_terms(); q++)
     592             :         {
     593             :           // Initialize the memory for the vectors
     594           0 :           non_dirichlet_Fq_vector[q] = NumericVector<Number>::build(this->comm());
     595           0 :           non_dirichlet_Fq_vector[q]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     596             :         }
     597             :     }
     598             : 
     599         852 :   for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
     600        1136 :     for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
     601             :       {
     602             :         // Initialize the memory for the truth output vectors
     603         568 :         outputs_vector[n][q_l] = NumericVector<Number>::build(this->comm());
     604         600 :         outputs_vector[n][q_l]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     605             :       }
     606             : 
     607         284 :   if (store_non_dirichlet_operators)
     608             :     {
     609           0 :       non_dirichlet_outputs_vector.resize(get_rb_theta_expansion().get_n_outputs());
     610           0 :       for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
     611             :         {
     612           0 :           non_dirichlet_outputs_vector[n].resize( get_rb_theta_expansion().get_n_output_terms(n) );
     613           0 :           for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
     614             :             {
     615             :               // Initialize the memory for the truth output vectors
     616           0 :               non_dirichlet_outputs_vector[n][q_l] = NumericVector<Number>::build(this->comm());
     617           0 :               non_dirichlet_outputs_vector[n][q_l]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     618             :             }
     619             :         }
     620             :     }
     621             : 
     622             :   // Resize truth_outputs vector
     623         284 :   truth_outputs.resize(this->get_rb_theta_expansion().get_n_outputs());
     624         284 : }
     625             : 
     626        2883 : std::unique_ptr<DGFEMContext> RBConstruction::build_context ()
     627             : {
     628        2883 :   return std::make_unique<DGFEMContext>(*this);
     629             : }
     630             : 
     631        2883 : void RBConstruction::add_scaled_matrix_and_vector(Number scalar,
     632             :                                                   ElemAssembly * elem_assembly,
     633             :                                                   SparseMatrix<Number> * input_matrix,
     634             :                                                   NumericVector<Number> * input_vector,
     635             :                                                   bool symmetrize,
     636             :                                                   bool apply_dof_constraints)
     637             : {
     638          80 :   LOG_SCOPE("add_scaled_matrix_and_vector()", "RBConstruction");
     639             : 
     640        2883 :   bool assemble_matrix = (input_matrix != nullptr);
     641          80 :   bool assemble_vector = (input_vector != nullptr);
     642             : 
     643        2883 :   if (!assemble_matrix && !assemble_vector)
     644           0 :     return;
     645             : 
     646         160 :   const MeshBase & mesh = this->get_mesh();
     647             : 
     648             :   // First add any node-based terms (e.g. point loads)
     649             : 
     650             :   // Make a std::set of all the nodes that are in 1 or more
     651             :   // nodesets. We only want to call get_nodal_values() once per Node
     652             :   // per ElemAssembly object, regardless of how many nodesets it
     653             :   // appears in.
     654         160 :   std::set<dof_id_type> nodes_with_nodesets;
     655      779730 :   for (const auto & t : mesh.get_boundary_info().build_node_list())
     656      715147 :     nodes_with_nodesets.insert(std::get<0>(t));
     657             : 
     658             :   // It's possible for the node assembly loop below to throw an
     659             :   // exception on one (or some subset) of the processors. In that
     660             :   // case, we stop assembling on the processor(s) that threw and let
     661             :   // the other processors finish assembly. Then we synchronize to
     662             :   // check whether an exception was thrown on _any_ processor, and if
     663             :   // so, re-throw it on _all_ processors.
     664        2883 :   int nodal_assembly_threw = 0;
     665             : 
     666             :   libmesh_try
     667             :   {
     668             : 
     669      702238 :   for (const auto & id : nodes_with_nodesets)
     670             :     {
     671      699355 :       const Node & node = mesh.node_ref(id);
     672             : 
     673             :       // If node is on this processor, then all dofs on node are too
     674             :       // so we can do the add below safely
     675      699355 :       if (node.processor_id() == this->comm().rank())
     676             :         {
     677             :           // Get the values to add to the rhs vector
     678       54800 :           std::vector<dof_id_type> nodal_dof_indices;
     679      387900 :           DenseMatrix<Number> nodal_matrix;
     680      333100 :           DenseVector<Number> nodal_rhs;
     681      333100 :           elem_assembly->get_nodal_values(nodal_dof_indices,
     682             :                                           nodal_matrix,
     683             :                                           nodal_rhs,
     684             :                                           *this,
     685       54800 :                                           node);
     686             : 
     687             :           // Perform any required user-defined postprocessing on
     688             :           // the matrix and rhs.
     689             :           //
     690             :           // TODO: We need to postprocess node matrices and vectors
     691             :           // in some cases (e.g. when rotations are applied to
     692             :           // nodes), but since we don't have a FEMContext at this
     693             :           // point we would need to have a different interface
     694             :           // taking the DenseMatrix, DenseVector, and probably the
     695             :           // current node that we are on...
     696             :           // this->post_process_elem_matrix_and_vector(nodal_matrix, nodal_rhs);
     697             : 
     698      333100 :           if (!nodal_dof_indices.empty())
     699             :             {
     700           0 :               if (apply_dof_constraints)
     701             :                 {
     702             :                   // Apply constraints, e.g. Dirichlet and periodic constraints
     703           0 :                   this->get_dof_map().constrain_element_matrix_and_vector(
     704             :                     nodal_matrix,
     705             :                     nodal_rhs,
     706             :                     nodal_dof_indices,
     707             :                     /*asymmetric_constraint_rows*/ false);
     708             :                 }
     709             : 
     710             :               // Scale and add to global matrix and/or vector
     711           0 :               nodal_matrix *= scalar;
     712           0 :               nodal_rhs    *= scalar;
     713             : 
     714           0 :               if (assemble_vector)
     715           0 :                 input_vector->add_vector(nodal_rhs, nodal_dof_indices);
     716             : 
     717           0 :               if (assemble_matrix)
     718           0 :                 input_matrix->add_matrix(nodal_matrix, nodal_dof_indices);
     719             :             }
     720      278300 :         }
     721             :     }
     722             :   } // libmesh_try
     723           0 :   libmesh_catch(...)
     724             :   {
     725           0 :     nodal_assembly_threw = 1;
     726           0 :   }
     727             : 
     728             :   // Check for exceptions on any procs and if there is one, re-throw
     729             :   // it on all procs.
     730        2883 :   this->comm().max(nodal_assembly_threw);
     731             : 
     732        2883 :   if (nodal_assembly_threw)
     733           0 :     libmesh_error_msg("Error during assembly in RBConstruction::add_scaled_matrix_and_vector()");
     734             : 
     735        2963 :   std::unique_ptr<DGFEMContext> c = this->build_context();
     736          80 :   DGFEMContext & context  = cast_ref<DGFEMContext &>(*c);
     737             : 
     738        2883 :   this->init_context(context);
     739             : 
     740             :   // It's possible for the assembly loop below to throw an exception
     741             :   // on one (or some subset) of the processors. This can happen when
     742             :   // e.g. the mesh contains one or more elements with negative
     743             :   // Jacobian. In that case, we stop assembling on the processor(s)
     744             :   // that threw and let the other processors finish assembly. Then we
     745             :   // synchronize to check whether an exception was thrown on _any_
     746             :   // processor, and if so, re-throw it on _all_ processors. This way,
     747             :   // we make it easier for callers to handle exceptions in parallel
     748             :   // during assembly: they can simply assume that the code either
     749             :   // throws on all procs or on no procs.
     750        2883 :   int assembly_threw = 0;
     751             : 
     752             :   libmesh_try
     753             :   {
     754             : 
     755      837686 :   for (const auto & elem : mesh.active_local_element_ptr_range())
     756             :     {
     757      451375 :       const ElemType elemtype = elem->type();
     758             : 
     759      451375 :       if(elemtype == NODEELEM)
     760             :         {
     761             :           // We assume that we do not perform any assembly directly on
     762             :           // NodeElems, so we skip the assembly calls.
     763             : 
     764             :           // However, in a spline basis with Dirichlet constraints on
     765             :           // spline nodes, a constrained matrix has to take those
     766             :           // nodes into account.
     767           0 :           if (!apply_dof_constraints)
     768           0 :             continue;
     769             :         }
     770             : 
     771             :       // Subdivision elements need special care:
     772             :       // - skip ghost elements
     773             :       // - init special quadrature rule
     774      380625 :       std::unique_ptr<QBase> qrule;
     775      451375 :       if (elemtype == TRI3SUBDIVISION)
     776             :         {
     777           0 :           const Tri3Subdivision * gh_elem = static_cast<const Tri3Subdivision *> (elem);
     778           0 :           if (gh_elem->is_ghost())
     779           0 :             continue ;
     780             :           // A Gauss quadrature rule for numerical integration.
     781             :           // For subdivision shell elements, a single Gauss point per
     782             :           // element is sufficient, hence we use extraorder = 0.
     783           0 :           const int extraorder = 0;
     784           0 :           FEBase * elem_fe = nullptr;
     785           0 :           context.get_element_fe( 0, elem_fe );
     786             : 
     787           0 :           qrule = elem_fe->get_fe_type().default_quadrature_rule (2, extraorder);
     788             : 
     789             :           // Tell the finite element object to use our quadrature rule.
     790           0 :           elem_fe->attach_quadrature_rule (qrule.get());
     791             :         }
     792             : 
     793      451375 :       context.pre_fe_reinit(*this, elem);
     794             : 
     795             :       // Do nothing in case there are no dof_indices on the current element
     796      451375 :       if ( context.get_dof_indices().empty() )
     797           0 :         continue;
     798             : 
     799      451375 :       context.elem_fe_reinit();
     800             : 
     801      451375 :       if (elemtype != NODEELEM)
     802             :         {
     803      451375 :           elem_assembly->interior_assembly(context);
     804             : 
     805      451375 :           const unsigned char n_sides = context.get_elem().n_sides();
     806     2400875 :           for (context.side = 0; context.side != n_sides; ++context.side)
     807             :             {
     808             :               // May not need to apply fluxes on non-boundary elements
     809     2103000 :               if ((context.get_elem().neighbor_ptr(context.get_side()) != nullptr) && !impose_internal_fluxes)
     810     1684120 :                 continue;
     811             : 
     812             :               // skip degenerate sides with zero area
     813      131440 :               if( (context.get_elem().side_ptr(context.get_side())->volume() <= 0.) && skip_degenerate_sides)
     814           0 :                 continue;
     815             : 
     816      121660 :               context.side_fe_reinit();
     817      121660 :               elem_assembly->boundary_assembly(context);
     818             : 
     819      121660 :               if (context.dg_terms_are_active())
     820             :                 {
     821           0 :                   input_matrix->add_matrix (context.get_elem_elem_jacobian(),
     822           0 :                                             context.get_dof_indices(),
     823           0 :                                             context.get_dof_indices());
     824             : 
     825           0 :                   input_matrix->add_matrix (context.get_elem_neighbor_jacobian(),
     826           0 :                                             context.get_dof_indices(),
     827           0 :                                             context.get_neighbor_dof_indices());
     828             : 
     829           0 :                   input_matrix->add_matrix (context.get_neighbor_elem_jacobian(),
     830             :                                             context.get_neighbor_dof_indices(),
     831           0 :                                             context.get_dof_indices());
     832             : 
     833           0 :                   input_matrix->add_matrix (context.get_neighbor_neighbor_jacobian(),
     834             :                                             context.get_neighbor_dof_indices(),
     835           0 :                                             context.get_neighbor_dof_indices());
     836             :                 }
     837             :             }
     838             :         }
     839             : 
     840             :       // Do any required user post-processing before symmetrizing and/or applying
     841             :       // constraints.
     842             :       //
     843             :       // We only do this if apply_dof_constraints is true because we want to be
     844             :       // able to set apply_dof_constraints=false in order to obtain a matrix
     845             :       // A with no dof constraints or dof transformations, as opposed to C^T A C,
     846             :       // which includes constraints and/or dof transformations. Here C refers to
     847             :       // the matrix that imposes dof constraints and transformations on the
     848             :       // solution u.
     849             :       //
     850             :       // Matrices such as A are what we store in our "non_dirichlet" operators, and
     851             :       // they are useful for computing terms such as (C u_i)^T A (C u_j) (e.g. see
     852             :       // update_RB_system_matrices()),  where C u is the result of a "truth_solve",
     853             :       // which includes calls to both enforce_constraints_exactly() and
     854             :       // post_process_truth_solution(). If we use C^T A C to compute these terms then
     855             :       // we would "double apply" the matrix C, which can give incorrect results.
     856      451375 :       if (apply_dof_constraints)
     857      451375 :         this->post_process_elem_matrix_and_vector(context);
     858             : 
     859             :       // Need to symmetrize before imposing
     860             :       // periodic constraints
     861      451375 :       if (assemble_matrix && symmetrize)
     862             :         {
     863       28125 :           DenseMatrix<Number> Ke_transpose;
     864       28125 :           context.get_elem_jacobian().get_transpose(Ke_transpose);
     865       28125 :           context.get_elem_jacobian() += Ke_transpose;
     866           0 :           context.get_elem_jacobian() *= 0.5;
     867       28125 :         }
     868             : 
     869             :       // As discussed above, we can set apply_dof_constraints=false to
     870             :       // get A instead of C^T A C
     871      451375 :       if (apply_dof_constraints)
     872             :         {
     873             :           // Apply constraints, e.g. Dirichlet and periodic constraints
     874       35375 :           this->get_dof_map().constrain_element_matrix_and_vector
     875      451375 :             (context.get_elem_jacobian(),
     876             :              context.get_elem_residual(),
     877             :              context.get_dof_indices(),
     878             :              /*asymmetric_constraint_rows*/ false );
     879             :         }
     880             : 
     881             :       // Scale and add to global matrix and/or vector
     882       63875 :       context.get_elem_jacobian() *= scalar;
     883       63875 :       context.get_elem_residual() *= scalar;
     884             : 
     885      451375 :       if (assemble_matrix)
     886             :         {
     887             : 
     888      220675 :           CouplingMatrix * coupling_matrix = get_dof_map()._dof_coupling;
     889      220675 :           if (!coupling_matrix)
     890             :             {
     891             :               // If we haven't defined a _dof_coupling matrix then just add
     892             :               // the whole matrix
     893      220675 :               input_matrix->add_matrix (context.get_elem_jacobian(),
     894       32300 :                                         context.get_dof_indices() );
     895             :             }
     896             :           else
     897             :             {
     898             :               // Otherwise we should only add the relevant submatrices
     899           0 :               for (unsigned int var1=0; var1<n_vars(); var1++)
     900             :                 {
     901           0 :                   ConstCouplingRow ccr(var1, *coupling_matrix);
     902           0 :                   for (const auto & var2 : ccr)
     903             :                     {
     904           0 :                       unsigned int sub_m = context.get_elem_jacobian( var1, var2 ).m();
     905           0 :                       unsigned int sub_n = context.get_elem_jacobian( var1, var2 ).n();
     906           0 :                       DenseMatrix<Number> sub_jac(sub_m, sub_n);
     907           0 :                       for (unsigned int row=0; row<sub_m; row++)
     908           0 :                         for (unsigned int col=0; col<sub_n; col++)
     909             :                           {
     910           0 :                             sub_jac(row,col) = context.get_elem_jacobian( var1, var2 ).el(row,col);
     911             :                           }
     912           0 :                       input_matrix->add_matrix (sub_jac,
     913           0 :                                                 context.get_dof_indices(var1),
     914           0 :                                                 context.get_dof_indices(var2) );
     915           0 :                     }
     916             :                 }
     917             :             }
     918             : 
     919             :         }
     920             : 
     921      451375 :       if (assemble_vector)
     922      211475 :         input_vector->add_vector (context.get_elem_residual(),
     923       19225 :                                   context.get_dof_indices() );
     924      383348 :     } // end for (elem)
     925             : 
     926             :   } // libmesh_try
     927           0 :   libmesh_catch(...)
     928             :   {
     929           0 :     assembly_threw = 1;
     930           0 :   }
     931             : 
     932             :   // Note: regardless of whether any procs threw during assembly (and
     933             :   // thus didn't finish assembling), we should not leave the matrix
     934             :   // and vector in an inconsistent state, since it may be possible to
     935             :   // recover from the exception. Therefore, we close them now. The
     936             :   // assumption here is that the nature of the exception does not
     937             :   // prevent the matrix and vector from still being assembled (albeit
     938             :   // with incomplete data).
     939        2883 :   if (assemble_matrix)
     940        1321 :     input_matrix->close();
     941        2883 :   if (assemble_vector)
     942        1562 :     input_vector->close();
     943             : 
     944             :   // Check for exceptions on any procs and if there is one, re-throw
     945             :   // it on all procs.
     946        2883 :   this->comm().max(assembly_threw);
     947             : 
     948        2883 :   if (assembly_threw)
     949           0 :     libmesh_error_msg("Error during assembly in RBConstruction::add_scaled_matrix_and_vector()");
     950        2723 : }
     951             : 
     952           0 : void RBConstruction::set_context_solution_vec(NumericVector<Number> & vec)
     953             : {
     954             :   // Set current_local_solution = vec so that we can access
     955             :   // vec from DGFEMContext during assembly
     956             :   vec.localize
     957           0 :     (*current_local_solution, this->get_dof_map().get_send_list());
     958           0 : }
     959             : 
     960        3556 : void RBConstruction::truth_assembly()
     961             : {
     962         200 :   LOG_SCOPE("truth_assembly()", "RBConstruction");
     963             : 
     964        3556 :   const RBParameters & mu = get_parameters();
     965             : 
     966        3556 :   this->matrix->zero();
     967        3556 :   this->rhs->zero();
     968             : 
     969        3556 :   this->matrix->close();
     970        3556 :   this->rhs->close();
     971             : 
     972             :   {
     973             :     // We should have already assembled the matrices
     974             :     // and vectors in the affine expansion, so
     975             :     // just use them
     976             : 
     977       14224 :     for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
     978             :       {
     979       10668 :         matrix->add(get_rb_theta_expansion().eval_A_theta(q_a, mu), *get_Aq(q_a));
     980             :       }
     981             : 
     982        3656 :     std::unique_ptr<NumericVector<Number>> temp_vec = NumericVector<Number>::build(this->comm());
     983        3556 :     temp_vec->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
     984       17762 :     for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
     985             :       {
     986       14206 :         *temp_vec = *get_Fq(q_f);
     987       14206 :         temp_vec->scale( get_rb_theta_expansion().eval_F_theta(q_f, mu) );
     988       14606 :         rhs->add(*temp_vec);
     989             :       }
     990        3356 :   }
     991             : 
     992        3556 :   this->matrix->close();
     993        3556 :   this->rhs->close();
     994        3556 : }
     995             : 
     996         284 : void RBConstruction::assemble_inner_product_matrix(SparseMatrix<Number> * input_matrix,
     997             :                                                    bool apply_dof_constraints)
     998             : {
     999         284 :   input_matrix->zero();
    1000             : 
    1001         284 :   if(!use_energy_inner_product)
    1002             :   {
    1003         284 :     add_scaled_matrix_and_vector(1.,
    1004             :                                  inner_product_assembly,
    1005             :                                  input_matrix,
    1006             :                                  nullptr,
    1007             :                                  false, /* symmetrize */
    1008             :                                  apply_dof_constraints);
    1009             :   }
    1010             :   else
    1011             :   {
    1012           0 :     libmesh_error_msg_if(energy_inner_product_coeffs.size() != get_rb_theta_expansion().get_n_A_terms(),
    1013             :                          "Error: invalid number of entries in energy_inner_product_coeffs.");
    1014             : 
    1015             :     // We symmetrize below so that we may use the energy inner-product even in cases
    1016             :     // where the A_q are not symmetric.
    1017           0 :     for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    1018             :       {
    1019           0 :         add_scaled_matrix_and_vector(energy_inner_product_coeffs[q_a],
    1020           0 :                                      &rb_assembly_expansion->get_A_assembly(q_a),
    1021             :                                      input_matrix,
    1022             :                                      nullptr,
    1023             :                                      true, /* symmetrize */
    1024             :                                      apply_dof_constraints);
    1025             :       }
    1026             :   }
    1027         284 : }
    1028             : 
    1029         852 : void RBConstruction::assemble_Aq_matrix(unsigned int q,
    1030             :                                         SparseMatrix<Number> * input_matrix,
    1031             :                                         bool apply_dof_constraints)
    1032             : {
    1033         852 :   libmesh_error_msg_if(q >= get_rb_theta_expansion().get_n_A_terms(),
    1034             :                        "Error: We must have q < Q_a in assemble_Aq_matrix.");
    1035             : 
    1036         852 :   input_matrix->zero();
    1037             : 
    1038         876 :   add_scaled_matrix_and_vector(1.,
    1039         852 :                                &rb_assembly_expansion->get_A_assembly(q),
    1040             :                                input_matrix,
    1041             :                                nullptr,
    1042             :                                false, /* symmetrize */
    1043             :                                apply_dof_constraints);
    1044         852 : }
    1045             : 
    1046          45 : void RBConstruction::add_scaled_Aq(Number scalar,
    1047             :                                    unsigned int q_a,
    1048             :                                    SparseMatrix<Number> * input_matrix,
    1049             :                                    bool symmetrize)
    1050             : {
    1051           0 :   LOG_SCOPE("add_scaled_Aq()", "RBConstruction");
    1052             : 
    1053          45 :   libmesh_error_msg_if(q_a >= get_rb_theta_expansion().get_n_A_terms(),
    1054             :                        "Error: We must have q < Q_a in add_scaled_Aq.");
    1055             : 
    1056          45 :   if (!symmetrize)
    1057             :     {
    1058           0 :       input_matrix->add(scalar, *get_Aq(q_a));
    1059           0 :       input_matrix->close();
    1060             :     }
    1061             :   else
    1062             :     {
    1063          45 :       add_scaled_matrix_and_vector(scalar,
    1064          45 :                                    &rb_assembly_expansion->get_A_assembly(q_a),
    1065             :                                    input_matrix,
    1066             :                                    nullptr,
    1067             :                                    symmetrize);
    1068             :     }
    1069          45 : }
    1070             : 
    1071         284 : void RBConstruction::assemble_misc_matrices()
    1072             : {
    1073           8 :   libMesh::out << "Assembling inner product matrix" << std::endl;
    1074         284 :   assemble_inner_product_matrix(inner_product_matrix.get());
    1075             : 
    1076         284 :   if (store_non_dirichlet_operators)
    1077             :     {
    1078           0 :       libMesh::out << "Assembling non-Dirichlet inner product matrix" << std::endl;
    1079           0 :       assemble_inner_product_matrix(non_dirichlet_inner_product_matrix.get(), false);
    1080             :     }
    1081         284 : }
    1082             : 
    1083         284 : void RBConstruction::assemble_all_affine_operators()
    1084             : {
    1085        1136 :   for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    1086             :     {
    1087         852 :       libMesh::out << "Assembling affine operator " << (q_a+1) << " of "
    1088         852 :                    << get_rb_theta_expansion().get_n_A_terms() << std::endl;
    1089         852 :       assemble_Aq_matrix(q_a, get_Aq(q_a));
    1090             :     }
    1091             : 
    1092         284 :   if (store_non_dirichlet_operators)
    1093             :     {
    1094           0 :       for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    1095             :         {
    1096           0 :           libMesh::out << "Assembling non-Dirichlet affine operator " << (q_a+1) << " of "
    1097           0 :                        << get_rb_theta_expansion().get_n_A_terms() << std::endl;
    1098           0 :           assemble_Aq_matrix(q_a, get_non_dirichlet_Aq(q_a), false);
    1099             :         }
    1100             :     }
    1101         284 : }
    1102             : 
    1103         284 : void RBConstruction::assemble_all_affine_vectors()
    1104             : {
    1105        1278 :   for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    1106             :     {
    1107         994 :       libMesh::out << "Assembling affine vector " << (q_f+1) << " of "
    1108         994 :                    << get_rb_theta_expansion().get_n_F_terms() << std::endl;
    1109         994 :       assemble_Fq_vector(q_f, get_Fq(q_f));
    1110             :     }
    1111             : 
    1112         284 :   if (store_non_dirichlet_operators)
    1113             :     {
    1114           0 :       for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    1115             :         {
    1116           0 :           libMesh::out << "Assembling non-Dirichlet affine vector " << (q_f+1) << " of "
    1117           0 :                        << get_rb_theta_expansion().get_n_F_terms() << std::endl;
    1118           0 :           assemble_Fq_vector(q_f, get_non_dirichlet_Fq(q_f), false);
    1119             :         }
    1120             :     }
    1121             : 
    1122         284 : }
    1123             : 
    1124         994 : void RBConstruction::assemble_Fq_vector(unsigned int q,
    1125             :                                         NumericVector<Number> * input_vector,
    1126             :                                         bool apply_dof_constraints)
    1127             : {
    1128         994 :   libmesh_error_msg_if(q >= get_rb_theta_expansion().get_n_F_terms(),
    1129             :                        "Error: We must have q < Q_f in assemble_Fq_vector.");
    1130             : 
    1131         994 :   input_vector->zero();
    1132             : 
    1133        1022 :   add_scaled_matrix_and_vector(1.,
    1134         994 :                                &rb_assembly_expansion->get_F_assembly(q),
    1135             :                                nullptr,
    1136             :                                input_vector,
    1137             :                                false,             /* symmetrize */
    1138             :                                apply_dof_constraints /* apply_dof_constraints */);
    1139         994 : }
    1140             : 
    1141         284 : void RBConstruction::assemble_all_output_vectors()
    1142             : {
    1143         852 :   for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    1144        1136 :     for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
    1145             :       {
    1146        1120 :         libMesh::out << "Assembling output vector, (" << (n+1) << "," << (q_l+1)
    1147         568 :                      << ") of (" << get_rb_theta_expansion().get_n_outputs()
    1148         568 :                      << "," << get_rb_theta_expansion().get_n_output_terms(n) << ")"
    1149          16 :                      << std::endl;
    1150         568 :         get_output_vector(n, q_l)->zero();
    1151         568 :         add_scaled_matrix_and_vector(1., &rb_assembly_expansion->get_output_assembly(n,q_l),
    1152             :                                      nullptr,
    1153             :                                      get_output_vector(n,q_l),
    1154             :                                      false, /* symmetrize */
    1155             :                                      true   /* apply_dof_constraints */);
    1156             :       }
    1157             : 
    1158         284 :   if (store_non_dirichlet_operators)
    1159             :     {
    1160           0 :       for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    1161           0 :         for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
    1162             :           {
    1163           0 :             libMesh::out << "Assembling non-Dirichlet output vector, (" << (n+1) << "," << (q_l+1)
    1164           0 :                          << ") of (" << get_rb_theta_expansion().get_n_outputs()
    1165           0 :                          << "," << get_rb_theta_expansion().get_n_output_terms(n) << ")"
    1166           0 :                          << std::endl;
    1167           0 :             get_non_dirichlet_output_vector(n, q_l)->zero();
    1168           0 :             add_scaled_matrix_and_vector(1., &rb_assembly_expansion->get_output_assembly(n,q_l),
    1169             :                                          nullptr,
    1170             :                                          get_non_dirichlet_output_vector(n,q_l),
    1171             :                                          false, /* symmetrize */
    1172             :                                          false  /* apply_dof_constraints */);
    1173             :           }
    1174             :     }
    1175         284 : }
    1176             : 
    1177         284 : Real RBConstruction::train_reduced_basis(const bool resize_rb_eval_data)
    1178             : {
    1179         560 :   if(get_RB_training_type() == "Greedy")
    1180             :     {
    1181         284 :       return train_reduced_basis_with_greedy(resize_rb_eval_data);
    1182             :     }
    1183           0 :   else if (get_RB_training_type() == "POD")
    1184             :     {
    1185           0 :       train_reduced_basis_with_POD();
    1186           0 :       return 0.;
    1187             :     }
    1188             :   else
    1189             :     {
    1190           0 :       libmesh_error_msg("RB training type not recognized: " + get_RB_training_type());
    1191             :     }
    1192             : 
    1193             :   return 0.;
    1194             : }
    1195             : 
    1196         284 : Real RBConstruction::train_reduced_basis_with_greedy(const bool resize_rb_eval_data)
    1197             : {
    1198          16 :   LOG_SCOPE("train_reduced_basis_with_greedy()", "RBConstruction");
    1199             : 
    1200           8 :   int count = 0;
    1201             : 
    1202         284 :   RBEvaluation & rbe = get_rb_evaluation();
    1203             : 
    1204             :   // initialize rbe's parameters
    1205         284 :   rbe.initialize_parameters(*this);
    1206             : 
    1207             :   // possibly resize data structures according to Nmax
    1208         284 :   if (resize_rb_eval_data)
    1209         284 :     rbe.resize_data_structures(get_Nmax());
    1210             : 
    1211             :   // Clear the Greedy param list
    1212         284 :   for (auto & plist : rbe.greedy_param_list)
    1213           0 :     plist.clear();
    1214             : 
    1215         276 :   rbe.greedy_param_list.clear();
    1216             : 
    1217           8 :   Real training_greedy_error = 0.;
    1218             : 
    1219             : 
    1220             :   // If we are continuing from a previous training run,
    1221             :   // we might already be at the max number of basis functions.
    1222             :   // If so, we can just return.
    1223         284 :   if (rbe.get_n_basis_functions() >= get_Nmax())
    1224             :     {
    1225           0 :       libMesh::out << "Maximum number of basis functions reached: Nmax = "
    1226           0 :                    << get_Nmax() << std::endl;
    1227           0 :       return 0.;
    1228             :     }
    1229             : 
    1230             :   // Optionally pre-evaluate the theta functions on the entire (local) training parameter set.
    1231         284 :   if (get_preevaluate_thetas_flag())
    1232           0 :     preevaluate_thetas();
    1233             : 
    1234         284 :   if(!skip_residual_in_train_reduced_basis)
    1235             :     {
    1236             :       // Compute the dual norms of the outputs if we haven't already done so.
    1237         284 :       compute_output_dual_innerprods();
    1238             : 
    1239             :       // Compute the Fq Riesz representor dual norms if we haven't already done so.
    1240         284 :       compute_Fq_representor_innerprods();
    1241             :     }
    1242             : 
    1243           8 :   libMesh::out << std::endl << "---- Performing Greedy basis enrichment ----" << std::endl;
    1244           8 :   Real initial_greedy_error = 0.;
    1245           8 :   bool initial_greedy_error_initialized = false;
    1246             :   while (true)
    1247             :     {
    1248         140 :       libMesh::out << std::endl << "---- Basis dimension: "
    1249        4957 :                    << rbe.get_n_basis_functions() << " ----" << std::endl;
    1250             : 
    1251        4957 :       if (count > 0 || (count==0 && use_empty_rb_solve_in_greedy))
    1252             :         {
    1253         140 :           libMesh::out << "Performing RB solves on training set" << std::endl;
    1254        4957 :           training_greedy_error = compute_max_error_bound();
    1255             : 
    1256         140 :           libMesh::out << "Maximum error bound is " << training_greedy_error << std::endl << std::endl;
    1257             : 
    1258             :           // record the initial error
    1259        4957 :           if (!initial_greedy_error_initialized)
    1260             :             {
    1261           8 :               initial_greedy_error = training_greedy_error;
    1262           8 :               initial_greedy_error_initialized = true;
    1263             :             }
    1264             : 
    1265             :           // Break out of training phase if we have reached Nmax
    1266             :           // or if the training_tolerance is satisfied.
    1267        4957 :           if (greedy_termination_test(training_greedy_error, initial_greedy_error, count))
    1268           0 :             break;
    1269             :         }
    1270             : 
    1271         140 :       libMesh::out << "Performing truth solve at parameter:" << std::endl;
    1272        4956 :       print_parameters();
    1273             : 
    1274             :       // Update the list of Greedily selected parameters
    1275        4956 :       this->update_greedy_param_list();
    1276             : 
    1277             :       // Perform an Offline truth solve for the current parameter
    1278        4956 :       truth_solve(-1);
    1279             : 
    1280        4956 :       if (check_if_zero_truth_solve())
    1281             :         {
    1282           0 :           libMesh::out << "Zero basis function encountered hence ending basis enrichment" << std::endl;
    1283           0 :           break;
    1284             :         }
    1285             : 
    1286             :       // Add orthogonal part of the snapshot to the RB space
    1287         140 :       libMesh::out << "Enriching the RB space" << std::endl;
    1288        4956 :       enrich_RB_space();
    1289             : 
    1290        4956 :       update_system();
    1291             : 
    1292             :       // Check if we've reached Nmax now. We do this before calling
    1293             :       // update_residual_terms() since we can skip that step if we've
    1294             :       // already reached Nmax.
    1295        4956 :       if (rbe.get_n_basis_functions() >= this->get_Nmax())
    1296             :       {
    1297           8 :         libMesh::out << "Maximum number of basis functions reached: Nmax = "
    1298          16 :                      << get_Nmax() << std::endl;
    1299           8 :         break;
    1300             :       }
    1301             : 
    1302        4673 :       if(!skip_residual_in_train_reduced_basis)
    1303             :         {
    1304        4673 :           update_residual_terms();
    1305             :         }
    1306             : 
    1307             :       // Increment counter
    1308        4673 :       count++;
    1309             :     }
    1310         284 :   this->update_greedy_param_list();
    1311             : 
    1312           8 :   return training_greedy_error;
    1313             : }
    1314             : 
    1315           0 : void RBConstruction::enrich_basis_from_rhs_terms(const bool resize_rb_eval_data)
    1316             : {
    1317           0 :   LOG_SCOPE("enrich_basis_from_rhs_terms()", "RBConstruction");
    1318             : 
    1319             :   // initialize rb_eval's parameters
    1320           0 :   get_rb_evaluation().initialize_parameters(*this);
    1321             : 
    1322             :   // possibly resize data structures according to Nmax
    1323           0 :   if (resize_rb_eval_data)
    1324             :     {
    1325           0 :       get_rb_evaluation().resize_data_structures(get_Nmax());
    1326             :     }
    1327             : 
    1328           0 :   libMesh::out << std::endl << "---- Enriching basis from rhs terms ----" << std::endl;
    1329             : 
    1330           0 :   truth_assembly();
    1331             : 
    1332           0 :   for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    1333             :     {
    1334           0 :       libMesh::out << std::endl << "Performing truth solve with rhs from rhs term " << q_f << std::endl;
    1335             : 
    1336           0 :       *rhs = *get_Fq(q_f);
    1337             : 
    1338           0 :       if (rhs->l2_norm() == 0)
    1339             :       {
    1340             :         // Skip enrichment if the rhs is zero
    1341           0 :         continue;
    1342             :       }
    1343             : 
    1344             :       // truth_assembly assembles into matrix and rhs, so use those for the solve
    1345           0 :       if (extra_linear_solver)
    1346             :         {
    1347             :           // If extra_linear_solver has been initialized, then we use it for the
    1348             :           // truth solves.
    1349           0 :           solve_for_matrix_and_rhs(*extra_linear_solver, *matrix, *rhs);
    1350             : 
    1351           0 :           if (assert_convergence)
    1352           0 :             check_convergence(*extra_linear_solver);
    1353             :         }
    1354             :       else
    1355             :         {
    1356           0 :           solve_for_matrix_and_rhs(*get_linear_solver(), *matrix, *rhs);
    1357             : 
    1358           0 :           if (assert_convergence)
    1359           0 :             check_convergence(*get_linear_solver());
    1360             :         }
    1361             : 
    1362             :       // Debugging: enable this code to print the rhs that was used in
    1363             :       // the most recent truth solve to a uniquely-named file.
    1364             : #if 0
    1365             :         {
    1366             :           char temp_file[] = "truth_rhs_XXXXXX.dat";
    1367             :           int fd = mkstemps(temp_file, 4);
    1368             :           if (fd != -1)
    1369             :             {
    1370             :               libMesh::out << "Writing truth system rhs to file: " << temp_file << std::endl;
    1371             :               rhs->print_matlab(std::string(temp_file));
    1372             :             }
    1373             :         }
    1374             : #endif // 0
    1375             : 
    1376             :       // Debugging: enable this code to print the most recent truth
    1377             :       // solution to a uniquely-named file.
    1378             : #ifdef LIBMESH_HAVE_EXODUS_API
    1379             : #if 0
    1380             :         {
    1381             :           // Note: mkstemps creates a file and returns an open file descriptor to it.
    1382             :           // The filename is created from a template which must have 6 'X' characters followed
    1383             :           // by a suffix having the specified length (in this case 4, for ".exo").
    1384             :           char temp_file[] = "truth_XXXXXX.exo";
    1385             :           int fd = mkstemps(temp_file, 4);
    1386             :           if (fd != -1)
    1387             :             {
    1388             :               libMesh::out << "Writing truth solution to file: " << temp_file << std::endl;
    1389             :               ExodusII_IO exo_io(this->get_mesh());
    1390             :               std::set<std::string> system_names = {this->name()};
    1391             :               exo_io.write_equation_systems(std::string(temp_file),
    1392             :                                             this->get_equation_systems(), &system_names);
    1393             :             }
    1394             :         }
    1395             : #endif // 0
    1396             : #endif // LIBMESH_HAVE_EXODUS_API
    1397             : 
    1398             :       // Call user-defined post-processing routines on the truth solution.
    1399           0 :       post_process_truth_solution();
    1400             : 
    1401             :       // Add orthogonal part of the snapshot to the RB space
    1402           0 :       libMesh::out << "Enriching the RB space" << std::endl;
    1403           0 :       enrich_RB_space();
    1404             : 
    1405           0 :       update_system();
    1406             :     }
    1407           0 : }
    1408             : 
    1409           0 : void RBConstruction::train_reduced_basis_with_POD()
    1410             : {
    1411             :   // We need to use the same training set on all processes so that
    1412             :   // the truth solves below work correctly in parallel.
    1413           0 :   libmesh_error_msg_if(!serial_training_set, "We must use a serial training set with POD");
    1414           0 :   libmesh_error_msg_if(get_rb_evaluation().get_n_basis_functions() > 0, "Basis should not already be initialized");
    1415             : 
    1416           0 :   get_rb_evaluation().initialize_parameters(*this);
    1417           0 :   get_rb_evaluation().resize_data_structures(get_Nmax());
    1418             : 
    1419             :   // Storage for the POD snapshots
    1420           0 :   unsigned int n_snapshots = get_n_training_samples();
    1421             : 
    1422           0 :   if (get_n_params() == 0)
    1423             :     {
    1424             :       // In this case we should have generated an empty training set
    1425             :       // so assert this
    1426           0 :       libmesh_assert(n_snapshots == 0);
    1427             : 
    1428             :       // If we have no parameters, then we should do exactly one "truth solve"
    1429           0 :       n_snapshots = 1;
    1430             :     }
    1431             : 
    1432           0 :   std::vector<std::unique_ptr<NumericVector<Number>>> POD_snapshots(n_snapshots);
    1433           0 :   for (unsigned int i=0; i<n_snapshots; i++)
    1434             :     {
    1435           0 :       POD_snapshots[i] = NumericVector<Number>::build(this->comm());
    1436           0 :       POD_snapshots[i]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    1437             :     }
    1438             : 
    1439             :   // We use the same training set on all processes
    1440           0 :   libMesh::out << std::endl;
    1441           0 :   for (unsigned int i=0; i<n_snapshots; i++)
    1442             :     {
    1443           0 :       if (get_n_params() > 0)
    1444             :         {
    1445           0 :           set_params_from_training_set(i);
    1446             :         }
    1447             : 
    1448           0 :       libMesh::out << "Truth solve " << (i+1) << " of " << n_snapshots << std::endl;
    1449             : 
    1450           0 :       truth_solve(-1);
    1451             : 
    1452           0 :       *POD_snapshots[i] = *solution;
    1453             :     }
    1454           0 :   libMesh::out << std::endl;
    1455             : 
    1456           0 :   if (_normalize_solution_snapshots)
    1457             :   {
    1458           0 :     libMesh::out << "Normalizing solution snapshots" << std::endl;
    1459           0 :     for (unsigned int i=0; i<n_snapshots; i++)
    1460             :       {
    1461           0 :         get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(
    1462           0 :           *inner_product_storage_vector, *POD_snapshots[i]);
    1463           0 :         Real norm = std::sqrt(std::real(POD_snapshots[i]->dot(*inner_product_storage_vector)));
    1464             : 
    1465           0 :         if (norm > 0.)
    1466           0 :           POD_snapshots[i]->scale(1./norm);
    1467             :       }
    1468             :   }
    1469             : 
    1470             :   // Set up the "correlation matrix"
    1471           0 :   DenseMatrix<Number> correlation_matrix(n_snapshots,n_snapshots);
    1472           0 :   for (unsigned int i=0; i<n_snapshots; i++)
    1473             :     {
    1474           0 :       get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(
    1475           0 :         *inner_product_storage_vector, *POD_snapshots[i]);
    1476             : 
    1477           0 :       for (unsigned int j=0; j<=i; j++)
    1478             :         {
    1479           0 :           Number inner_prod = (POD_snapshots[j]->dot(*inner_product_storage_vector));
    1480             : 
    1481           0 :           correlation_matrix(i,j) = inner_prod;
    1482           0 :           if(i != j)
    1483             :             {
    1484           0 :               correlation_matrix(j,i) = libmesh_conj(inner_prod);
    1485             :             }
    1486             :         }
    1487             :     }
    1488             : 
    1489             :   // compute SVD of correlation matrix
    1490           0 :   DenseVector<Real> sigma( n_snapshots );
    1491           0 :   DenseMatrix<Number> U( n_snapshots, n_snapshots );
    1492           0 :   DenseMatrix<Number> VT( n_snapshots, n_snapshots );
    1493           0 :   correlation_matrix.svd(sigma, U, VT );
    1494             : 
    1495           0 :   if (sigma(0) == 0.)
    1496           0 :     return;
    1497             : 
    1498             :   // Add dominant vectors from the POD as basis functions.
    1499           0 :   unsigned int j = 0;
    1500             :   while (true)
    1501             :     {
    1502           0 :       if (j >= get_Nmax() || j >= n_snapshots)
    1503             :         {
    1504           0 :           libMesh::out << "Maximum number of basis functions (" << j << ") reached." << std::endl;
    1505           0 :           break;
    1506             :         }
    1507             : 
    1508             :       // The "energy" error in the POD approximation is determined by the first omitted
    1509             :       // singular value, i.e. sigma(j). We normalize by sigma(0), which gives the total
    1510             :       // "energy", in order to obtain a relative error.
    1511           0 :       const Real rel_err = std::sqrt(sigma(j)) / std::sqrt(sigma(0));
    1512             : 
    1513           0 :       libMesh::out << "Number of basis functions: " << j
    1514           0 :                    << ", POD error norm: " << rel_err << std::endl;
    1515             : 
    1516           0 :       if (rel_err < this->rel_training_tolerance)
    1517             :         {
    1518           0 :           libMesh::out << "Training tolerance reached." << std::endl;
    1519           0 :           break;
    1520             :         }
    1521             : 
    1522           0 :       std::unique_ptr< NumericVector<Number> > v = POD_snapshots[j]->zero_clone();
    1523           0 :       for ( unsigned int i=0; i<n_snapshots; ++i )
    1524             :         {
    1525           0 :           v->add( U.el(i, j), *POD_snapshots[i] );
    1526             :         }
    1527             : 
    1528           0 :       Real norm_v = std::sqrt(sigma(j));
    1529           0 :       v->scale( 1./norm_v );
    1530             : 
    1531           0 :       get_rb_evaluation().basis_functions.emplace_back( std::move(v) );
    1532             : 
    1533           0 :       j++;
    1534           0 :     }
    1535           0 :   libMesh::out << std::endl;
    1536             : 
    1537           0 :   this->delta_N = get_rb_evaluation().get_n_basis_functions();
    1538           0 :   update_system();
    1539             : 
    1540             :   // We now compute all terms required to evaluate the RB error indicator.
    1541             :   // Unlike in the case of the RB Greedy algorithm, for the POD approach
    1542             :   // we do not need this data in order to compute the basis. However, we
    1543             :   // do need this data in order to evaluate error indicator quantities in
    1544             :   // the Online stage, so we compute it now so that it can be saved in
    1545             :   // the training data.
    1546           0 :   recompute_all_residual_terms();
    1547           0 : }
    1548             : 
    1549        4957 : bool RBConstruction::greedy_termination_test(Real abs_greedy_error,
    1550             :                                              Real initial_error,
    1551             :                                              int)
    1552             : {
    1553        4957 :   if (abs_greedy_error < this->abs_training_tolerance)
    1554             :     {
    1555           0 :       libMesh::out << "Absolute error tolerance reached." << std::endl;
    1556           0 :       return true;
    1557             :     }
    1558             : 
    1559        4957 :   Real rel_greedy_error = abs_greedy_error/initial_error;
    1560        4957 :   if (rel_greedy_error < this->rel_training_tolerance)
    1561             :     {
    1562           0 :       libMesh::out << "Relative error tolerance reached." << std::endl;
    1563           1 :       return true;
    1564             :     }
    1565             : 
    1566        4956 :   RBEvaluation & rbe = get_rb_evaluation();
    1567             : 
    1568        4956 :   if (rbe.get_n_basis_functions() >= this->get_Nmax())
    1569             :     {
    1570           0 :       libMesh::out << "Maximum number of basis functions reached: Nmax = "
    1571           0 :                    << get_Nmax() << std::endl;
    1572           0 :       return true;
    1573             :     }
    1574             : 
    1575        4956 :   if (exit_on_repeated_greedy_parameters)
    1576       31971 :     for (auto & plist : rbe.greedy_param_list)
    1577       28415 :       if (plist == get_parameters())
    1578             :         {
    1579           0 :           libMesh::out << "Exiting greedy because the same parameters were selected twice" << std::endl;
    1580           0 :           return true;
    1581             :         }
    1582             : 
    1583         140 :   return false;
    1584             : }
    1585             : 
    1586        5240 : void RBConstruction::update_greedy_param_list()
    1587             : {
    1588        5240 :   get_rb_evaluation().greedy_param_list.push_back( get_parameters() );
    1589        5240 : }
    1590             : 
    1591           0 : const RBParameters & RBConstruction::get_greedy_parameter(unsigned int i)
    1592             : {
    1593           0 :   libmesh_error_msg_if(i >= get_rb_evaluation().greedy_param_list.size(),
    1594             :                        "Error: Argument in RBConstruction::get_greedy_parameter is too large.");
    1595             : 
    1596           0 :   return get_rb_evaluation().greedy_param_list[i];
    1597             : }
    1598             : 
    1599        3556 : Real RBConstruction::truth_solve(int plot_solution)
    1600             : {
    1601         200 :   LOG_SCOPE("truth_solve()", "RBConstruction");
    1602             : 
    1603        3556 :   if(store_untransformed_basis && !_untransformed_solution)
    1604             :   {
    1605           0 :     _untransformed_solution = solution->zero_clone();
    1606             :   }
    1607             : 
    1608        3556 :   truth_assembly();
    1609             : 
    1610             :   // truth_assembly assembles into matrix and rhs, so use those for the solve
    1611        3556 :   if (extra_linear_solver)
    1612             :     {
    1613             :       // If extra_linear_solver has been initialized, then we use it for the
    1614             :       // truth solves.
    1615           0 :       solve_for_matrix_and_rhs(*extra_linear_solver, *matrix, *rhs);
    1616             : 
    1617           0 :       if (assert_convergence)
    1618           0 :         check_convergence(*extra_linear_solver);
    1619             :     }
    1620             :   else
    1621             :     {
    1622        3556 :       solve_for_matrix_and_rhs(*get_linear_solver(), *matrix, *rhs);
    1623             : 
    1624        3556 :       if (assert_convergence)
    1625        3556 :         check_convergence(*get_linear_solver());
    1626             :     }
    1627             : 
    1628        3556 :   if (store_untransformed_basis)
    1629             :     {
    1630           0 :       *_untransformed_solution = *solution;
    1631             :     }
    1632             : 
    1633             :   // Call user-defined post-processing routines on the truth solution.
    1634        3556 :   post_process_truth_solution();
    1635             : 
    1636        3556 :   const RBParameters & mu = get_parameters();
    1637             : 
    1638        9260 :   for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    1639             :     {
    1640        5864 :       truth_outputs[n] = 0.;
    1641       11408 :       for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
    1642       11488 :         truth_outputs[n] += get_rb_theta_expansion().eval_output_theta(n, q_l, mu)*
    1643        5784 :           get_output_vector(n,q_l)->dot(*solution);
    1644             :     }
    1645             : 
    1646             : #ifdef LIBMESH_HAVE_EXODUS_API
    1647        3556 :   if (plot_solution > 0)
    1648             :     {
    1649           0 :       ExodusII_IO exo_io(this->get_mesh());
    1650           0 :       std::set<std::string> system_names = {this->name()};
    1651           0 :       exo_io.write_equation_systems("truth.exo", this->get_equation_systems(), &system_names);
    1652           0 :     }
    1653             : #else
    1654             :   libmesh_ignore(plot_solution);
    1655             : #endif
    1656             : 
    1657             :   // Get the X norm of the truth solution
    1658             :   // Useful for normalizing our true error data
    1659        3556 :   get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *solution);
    1660        3656 :   Number truth_X_norm = std::sqrt(inner_product_storage_vector->dot(*solution));
    1661             : 
    1662        3656 :   return libmesh_real(truth_X_norm);
    1663           0 : }
    1664             : 
    1665         284 : bool RBConstruction::is_serial_training_type(const std::string & RB_training_type_in)
    1666             : {
    1667         284 :   return (RB_training_type_in == "POD");
    1668             : }
    1669             : 
    1670         284 : void RBConstruction::set_RB_training_type(const std::string & RB_training_type_in)
    1671             : {
    1672         284 :   this->RB_training_type = RB_training_type_in;
    1673             : 
    1674         284 :   if(is_serial_training_type(RB_training_type_in))
    1675             :     {
    1676             :       // We need to use a serial training set (so that the training
    1677             :       // set is the same on all processes) if we're using POD
    1678           0 :       this->serial_training_set = true;
    1679             :     }
    1680         284 : }
    1681             : 
    1682         638 : const std::string & RBConstruction::get_RB_training_type() const
    1683             : {
    1684         638 :   return RB_training_type;
    1685             : }
    1686             : 
    1687         284 : void RBConstruction::set_Nmax(unsigned int Nmax_in)
    1688             : {
    1689         284 :   this->Nmax = Nmax_in;
    1690         284 : }
    1691             : 
    1692         142 : void RBConstruction::load_basis_function(unsigned int i)
    1693             : {
    1694           8 :   LOG_SCOPE("load_basis_function()", "RBConstruction");
    1695             : 
    1696           4 :   libmesh_assert_less (i, get_rb_evaluation().get_n_basis_functions());
    1697             : 
    1698         142 :   *solution = get_rb_evaluation().get_basis_function(i);
    1699             : 
    1700         142 :   this->update();
    1701         142 : }
    1702             : 
    1703        3556 : void RBConstruction::enrich_RB_space()
    1704             : {
    1705         200 :   LOG_SCOPE("enrich_RB_space()", "RBConstruction");
    1706             : 
    1707        3656 :   auto new_bf = solution->clone();
    1708             : 
    1709        3556 :   std::unique_ptr<NumericVector<Number>> new_untransformed_bf;
    1710        3556 :   if (store_untransformed_basis)
    1711             :     {
    1712           0 :       new_untransformed_bf = _untransformed_solution->clone();
    1713           0 :       libmesh_assert_equal_to(_untransformed_basis_functions.size(), get_rb_evaluation().get_n_basis_functions());
    1714             :     }
    1715             : 
    1716       31971 :   for (unsigned int index=0; index<get_rb_evaluation().get_n_basis_functions(); index++)
    1717             :     {
    1718       28415 :       get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *new_bf);
    1719             : 
    1720             :       Number scalar =
    1721       28415 :         inner_product_storage_vector->dot(get_rb_evaluation().get_basis_function(index));
    1722       28415 :       new_bf->add(-scalar, get_rb_evaluation().get_basis_function(index));
    1723             : 
    1724       28415 :       if (store_untransformed_basis)
    1725             :         {
    1726           0 :           new_untransformed_bf->add(-scalar, *_untransformed_basis_functions[index]);
    1727             :         }
    1728             :     }
    1729             : 
    1730             :   // Normalize new_bf
    1731        3556 :   get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *new_bf);
    1732        3656 :   Number new_bf_norm = std::sqrt( inner_product_storage_vector->dot(*new_bf) );
    1733             : 
    1734        3506 :   if (new_bf_norm == 0.)
    1735             :     {
    1736           0 :       new_bf->zero(); // avoid potential nan's
    1737             : 
    1738           0 :       if (store_untransformed_basis)
    1739             :         {
    1740           0 :           new_untransformed_bf->zero();
    1741             :         }
    1742             :     }
    1743             :   else
    1744             :     {
    1745        3556 :       new_bf->scale(1./new_bf_norm);
    1746             : 
    1747        3556 :       if (store_untransformed_basis)
    1748             :         {
    1749           0 :           new_untransformed_bf->scale(1./new_bf_norm);
    1750             :         }
    1751             :     }
    1752             : 
    1753             :   // load the new basis function into the basis_functions vector.
    1754        3556 :   get_rb_evaluation().basis_functions.emplace_back( std::move(new_bf) );
    1755             : 
    1756        3556 :   if (store_untransformed_basis)
    1757             :     {
    1758           0 :       _untransformed_basis_functions.emplace_back( std::move(new_untransformed_bf) );
    1759             :     }
    1760        3556 : }
    1761             : 
    1762        4956 : void RBConstruction::update_system()
    1763             : {
    1764         140 :   libMesh::out << "Updating RB matrices" << std::endl;
    1765        4956 :   update_RB_system_matrices();
    1766        4956 : }
    1767             : 
    1768      406700 : Real RBConstruction::get_RB_error_bound()
    1769             : {
    1770      406700 :   get_rb_evaluation().set_parameters( get_parameters() );
    1771             : 
    1772       34000 :   Real error_bound = 0.;
    1773      406700 :   if (get_preevaluate_thetas_flag())
    1774             :     {
    1775             :       // Obtain the pre-evaluated theta functions from the current training parameter index
    1776           0 :       const auto & evaluated_thetas = get_evaluated_thetas(get_current_training_parameter_index());
    1777           0 :       error_bound = get_rb_evaluation().rb_solve(get_rb_evaluation().get_n_basis_functions(),
    1778           0 :                                                  &evaluated_thetas);
    1779             :     }
    1780             :   else
    1781      406700 :     error_bound = get_rb_evaluation().rb_solve(get_rb_evaluation().get_n_basis_functions());
    1782             : 
    1783             : 
    1784      406700 :   if (normalize_rb_bound_in_greedy)
    1785             :     {
    1786           0 :       Real error_bound_normalization = get_rb_evaluation().get_error_bound_normalization();
    1787             : 
    1788           0 :       if ((error_bound < abs_training_tolerance) ||
    1789           0 :           (error_bound_normalization < abs_training_tolerance))
    1790             :         {
    1791             :           // We don't want to normalize this error bound if the bound or the
    1792             :           // normalization value are below the absolute tolerance. Hence do nothing
    1793             :           // in this case.
    1794             :         }
    1795             :       else
    1796           0 :         error_bound /= error_bound_normalization;
    1797             :     }
    1798             : 
    1799      406700 :   return error_bound;
    1800             : }
    1801             : 
    1802           0 : void RBConstruction::recompute_all_residual_terms(bool compute_inner_products)
    1803             : {
    1804             :   // Compute the basis independent terms
    1805           0 :   Fq_representor_innerprods_computed = false;
    1806           0 :   compute_Fq_representor_innerprods(compute_inner_products);
    1807             : 
    1808             :   // and all the basis dependent terms
    1809           0 :   unsigned int saved_delta_N = delta_N;
    1810           0 :   delta_N = get_rb_evaluation().get_n_basis_functions();
    1811             : 
    1812           0 :   update_residual_terms(compute_inner_products);
    1813             : 
    1814           0 :   delta_N = saved_delta_N;
    1815           0 : }
    1816             : 
    1817        4957 : Real RBConstruction::compute_max_error_bound()
    1818             : {
    1819         280 :   LOG_SCOPE("compute_max_error_bound()", "RBConstruction");
    1820             : 
    1821             :   // Treat the case with no parameters in a special way
    1822        4957 :   if (get_n_params() == 0)
    1823             :     {
    1824             :       Real max_val;
    1825             :       if (std::numeric_limits<Real>::has_infinity)
    1826             :         {
    1827           0 :           max_val = std::numeric_limits<Real>::infinity();
    1828             :         }
    1829             :       else
    1830             :         {
    1831             :           max_val = std::numeric_limits<Real>::max();
    1832             :         }
    1833             : 
    1834             :       // Make sure we do at least one solve, but otherwise return a zero error bound
    1835             :       // when we have no parameters
    1836           0 :       return (get_rb_evaluation().get_n_basis_functions() == 0) ? max_val : 0.;
    1837             :     }
    1838             : 
    1839        4957 :   training_error_bounds.resize(this->get_local_n_training_samples());
    1840             : 
    1841             :   // keep track of the maximum error
    1842         140 :   unsigned int max_err_index = 0;
    1843         140 :   Real max_err = 0.;
    1844             : 
    1845        4957 :   numeric_index_type first_index = get_first_local_training_index();
    1846      411657 :   for (unsigned int i=0; i<get_local_n_training_samples(); i++)
    1847             :     {
    1848             :       // Load training parameter i, this is only loaded
    1849             :       // locally since the RB solves are local.
    1850      406700 :       set_params_from_training_set( first_index+i );
    1851             : 
    1852             :       // In case we pre-evaluate the theta functions,
    1853             :       // also keep track of the current training parameter index.
    1854      406700 :       if (get_preevaluate_thetas_flag())
    1855           0 :         set_current_training_parameter_index(first_index+i);
    1856             : 
    1857             : 
    1858      406700 :       training_error_bounds[i] = get_RB_error_bound();
    1859             : 
    1860      406700 :       if (training_error_bounds[i] > max_err)
    1861             :         {
    1862         970 :           max_err_index = i;
    1863         970 :           max_err = training_error_bounds[i];
    1864             :         }
    1865             :     }
    1866             : 
    1867        4957 :   std::pair<numeric_index_type, Real> error_pair(first_index+max_err_index, max_err);
    1868        4957 :   get_global_max_error_pair(this->comm(),error_pair);
    1869             : 
    1870             :   // If we have a serial training set (i.e. a training set that is the same on all processors)
    1871             :   // just set the parameters on all processors
    1872        4957 :   if (serial_training_set)
    1873             :     {
    1874           0 :       set_params_from_training_set( error_pair.first );
    1875             :     }
    1876             :   // otherwise, broadcast the parameter that produced the maximum error
    1877             :   else
    1878             :     {
    1879        4957 :       unsigned int root_id=0;
    1880        7876 :       if ((get_first_local_training_index() <= error_pair.first) &&
    1881        2919 :           (error_pair.first < get_last_local_training_index()))
    1882             :         {
    1883         827 :           set_params_from_training_set( error_pair.first );
    1884         897 :           root_id = this->processor_id();
    1885             :         }
    1886             : 
    1887        4957 :       this->comm().sum(root_id); // root_id is only non-zero on one processor
    1888        4957 :       broadcast_parameters(root_id);
    1889             :     }
    1890             : 
    1891        4957 :   return error_pair.second;
    1892             : }
    1893             : 
    1894        4956 : void RBConstruction::update_RB_system_matrices()
    1895             : {
    1896         280 :   LOG_SCOPE("update_RB_system_matrices()", "RBConstruction");
    1897             : 
    1898        4956 :   unsigned int RB_size = get_rb_evaluation().get_n_basis_functions();
    1899             : 
    1900        5096 :   std::unique_ptr<NumericVector<Number>> temp = NumericVector<Number>::build(this->comm());
    1901        4956 :   temp->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    1902             : 
    1903       20562 :   for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    1904             :     {
    1905       31212 :       for (unsigned int i=(RB_size-delta_N); i<RB_size; i++)
    1906             :         {
    1907       15606 :           get_rb_evaluation().RB_Fq_vector[q_f](i) = get_non_dirichlet_Fq_if_avail(q_f)->dot(get_rb_evaluation().get_basis_function(i));
    1908             :         }
    1909             :     }
    1910             : 
    1911        9912 :   for (unsigned int i=(RB_size-delta_N); i<RB_size; i++)
    1912             :     {
    1913       16260 :       for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    1914       22608 :         for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
    1915             :           {
    1916       11304 :             get_rb_evaluation().RB_output_vectors[n][q_l](i) =
    1917       11304 :               get_output_vector(n,q_l)->dot(get_rb_evaluation().get_basis_function(i));
    1918             :           }
    1919             : 
    1920       51627 :       for (unsigned int j=0; j<RB_size; j++)
    1921             :         {
    1922        1320 :           Number value = 0.;
    1923             : 
    1924       46671 :           if (compute_RB_inner_product)
    1925             :             {
    1926             :               // Compute reduced inner_product_matrix
    1927       14700 :               temp->zero();
    1928       14700 :               get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*temp, get_rb_evaluation().get_basis_function(j));
    1929             : 
    1930       14700 :               value = temp->dot( get_rb_evaluation().get_basis_function(i) );
    1931       14700 :               get_rb_evaluation().RB_inner_product_matrix(i,j) = value;
    1932       14700 :               if (i!=j)
    1933             :                 {
    1934             :                   // The inner product matrix is assumed
    1935             :                   // to be hermitian
    1936       13300 :                   get_rb_evaluation().RB_inner_product_matrix(j,i) = libmesh_conj(value);
    1937             :                 }
    1938             :             }
    1939             : 
    1940      186684 :           for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    1941             :             {
    1942             :               // Compute reduced Aq matrix
    1943      140013 :               temp->zero();
    1944      140013 :               get_non_dirichlet_Aq_if_avail(q_a)->vector_mult(*temp, get_rb_evaluation().get_basis_function(j));
    1945             : 
    1946      140013 :               value = (*temp).dot(get_rb_evaluation().get_basis_function(i));
    1947      140013 :               get_rb_evaluation().RB_Aq_vector[q_a](i,j) = value;
    1948             : 
    1949      140013 :               if (i!=j)
    1950             :                 {
    1951      125145 :                   temp->zero();
    1952      125145 :                   get_non_dirichlet_Aq_if_avail(q_a)->vector_mult(*temp, get_rb_evaluation().get_basis_function(i));
    1953             : 
    1954      125145 :                   value = (*temp).dot(get_rb_evaluation().get_basis_function(j));
    1955      125145 :                   get_rb_evaluation().RB_Aq_vector[q_a](j,i) = value;
    1956             :                 }
    1957             :             }
    1958             :         }
    1959             :     }
    1960        4956 : }
    1961             : 
    1962             : 
    1963        4673 : void RBConstruction::update_residual_terms(bool compute_inner_products)
    1964             : {
    1965         264 :   LOG_SCOPE("update_residual_terms()", "RBConstruction");
    1966             : 
    1967         132 :   libMesh::out << "Updating RB residual terms" << std::endl;
    1968             : 
    1969        4673 :   unsigned int RB_size = get_rb_evaluation().get_n_basis_functions();
    1970             : 
    1971         132 :   if (store_untransformed_basis)
    1972             :     {
    1973           0 :       libmesh_assert_equal_to(_untransformed_basis_functions.size(), get_rb_evaluation().get_n_basis_functions());
    1974             :     }
    1975             : 
    1976       18692 :   for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    1977             :     {
    1978       28038 :       for (unsigned int i=(RB_size-delta_N); i<RB_size; i++)
    1979             :         {
    1980             :           // Initialize the vector in which we'll store the representor
    1981       14019 :           if (!get_rb_evaluation().Aq_representor[q_a][i])
    1982             :             {
    1983       27246 :               get_rb_evaluation().Aq_representor[q_a][i] = NumericVector<Number>::build(this->comm());
    1984       14019 :               get_rb_evaluation().Aq_representor[q_a][i]->init(this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    1985             :             }
    1986             : 
    1987         396 :           libmesh_assert(get_rb_evaluation().Aq_representor[q_a][i]->size()       == this->n_dofs()       &&
    1988             :                          get_rb_evaluation().Aq_representor[q_a][i]->local_size() == this->n_local_dofs() );
    1989             : 
    1990       14019 :           rhs->zero();
    1991       14019 :           if (!store_untransformed_basis)
    1992             :             {
    1993       14019 :               get_Aq(q_a)->vector_mult(*rhs, get_rb_evaluation().get_basis_function(i));
    1994             :             }
    1995             :           else
    1996             :             {
    1997           0 :               get_Aq(q_a)->vector_mult(*rhs, *_untransformed_basis_functions[i]);
    1998             :             }
    1999       14019 :           rhs->scale(-1.);
    2000             : 
    2001       14019 :           if (!is_quiet())
    2002             :             {
    2003           0 :               libMesh::out << "Starting solve [q_a][i]=[" << q_a <<"]["<< i << "] in RBConstruction::update_residual_terms() at "
    2004           0 :                            << Utility::get_timestamp() << std::endl;
    2005             :             }
    2006             : 
    2007       14415 :           solve_for_matrix_and_rhs(*inner_product_solver, *inner_product_matrix, *rhs);
    2008             : 
    2009       14019 :           if (assert_convergence)
    2010       14019 :             check_convergence(*inner_product_solver);
    2011             : 
    2012       14019 :           if (!is_quiet())
    2013             :             {
    2014           0 :               libMesh::out << "Finished solve [q_a][i]=[" << q_a <<"]["<< i << "] in RBConstruction::update_residual_terms() at "
    2015           0 :                            << Utility::get_timestamp() << std::endl;
    2016           0 :               libMesh::out << this->n_linear_iterations() << " iterations, final residual "
    2017           0 :                            << this->final_linear_residual() << std::endl;
    2018             :             }
    2019             : 
    2020             :           // Store the representor
    2021       14019 :           *get_rb_evaluation().Aq_representor[q_a][i] = *solution;
    2022             :         }
    2023             :     }
    2024             : 
    2025             :   // Now compute and store the inner products (if requested)
    2026        4673 :   if (compute_inner_products)
    2027             :     {
    2028             : 
    2029       19286 :       for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    2030             :         {
    2031       14613 :           get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector,*Fq_representor[q_f]);
    2032             : 
    2033       58452 :           for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    2034             :             {
    2035       87678 :               for (unsigned int i=(RB_size-delta_N); i<RB_size; i++)
    2036             :                 {
    2037       43839 :                   get_rb_evaluation().Fq_Aq_representor_innerprods[q_f][q_a][i] =
    2038       43839 :                     inner_product_storage_vector->dot(*get_rb_evaluation().Aq_representor[q_a][i]);
    2039             :                 }
    2040             :             }
    2041             :         }
    2042             : 
    2043         132 :       unsigned int q=0;
    2044       18692 :       for (unsigned int q_a1=0; q_a1<get_rb_theta_expansion().get_n_A_terms(); q_a1++)
    2045             :         {
    2046       42057 :           for (unsigned int q_a2=q_a1; q_a2<get_rb_theta_expansion().get_n_A_terms(); q_a2++)
    2047             :             {
    2048       56076 :               for (unsigned int i=(RB_size-delta_N); i<RB_size; i++)
    2049             :                 {
    2050      278364 :                   for (unsigned int j=0; j<RB_size; j++)
    2051             :                     {
    2052      250326 :                       get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *get_rb_evaluation().Aq_representor[q_a2][j]);
    2053      250326 :                       get_rb_evaluation().Aq_Aq_representor_innerprods[q][i][j] =
    2054      250326 :                         inner_product_storage_vector->dot(*get_rb_evaluation().Aq_representor[q_a1][i]);
    2055             : 
    2056      250326 :                       if (i != j)
    2057             :                         {
    2058      222288 :                           get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *get_rb_evaluation().Aq_representor[q_a2][i]);
    2059      222288 :                           get_rb_evaluation().Aq_Aq_representor_innerprods[q][j][i] =
    2060      222288 :                             inner_product_storage_vector->dot(*get_rb_evaluation().Aq_representor[q_a1][j]);
    2061             :                         }
    2062             :                     }
    2063             :                 }
    2064       28038 :               q++;
    2065             :             }
    2066             :         }
    2067             :     } // end if (compute_inner_products)
    2068        4673 : }
    2069             : 
    2070         576 : SparseMatrix<Number> & RBConstruction::get_matrix_for_output_dual_solves()
    2071             : {
    2072         576 :   return *inner_product_matrix;
    2073             : }
    2074             : 
    2075         284 : void RBConstruction::compute_output_dual_innerprods()
    2076             : {
    2077             :   // Skip calculations if we've already computed the output dual norms
    2078         284 :   if (!output_dual_innerprods_computed)
    2079             :     {
    2080             :       // Short circuit if we don't have any outputs
    2081         284 :       if (get_rb_theta_expansion().get_n_outputs() == 0)
    2082             :         {
    2083         142 :           output_dual_innerprods_computed = true;
    2084         142 :           return;
    2085             :         }
    2086             : 
    2087             :       // Only log if we get to here
    2088           8 :       LOG_SCOPE("compute_output_dual_innerprods()", "RBConstruction");
    2089             : 
    2090           4 :       libMesh::out << "Compute output dual inner products" << std::endl;
    2091             : 
    2092             :       // Find out the largest value of Q_l
    2093           4 :       unsigned int max_Q_l = 0;
    2094         710 :       for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    2095         568 :         max_Q_l = (get_rb_theta_expansion().get_n_output_terms(n) > max_Q_l) ? get_rb_theta_expansion().get_n_output_terms(n) : max_Q_l;
    2096             : 
    2097         142 :       std::vector<std::unique_ptr<NumericVector<Number>>> L_q_representor(max_Q_l);
    2098         284 :       for (unsigned int q=0; q<max_Q_l; q++)
    2099             :         {
    2100         142 :           L_q_representor[q] = NumericVector<Number>::build(this->comm());
    2101         146 :           L_q_representor[q]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    2102             :         }
    2103             : 
    2104         710 :       for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    2105             :         {
    2106        1136 :           for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
    2107             :             {
    2108         568 :               rhs->zero();
    2109         568 :               rhs->add(1., *get_output_vector(n,q_l));
    2110             : 
    2111         568 :               if (!is_quiet())
    2112           0 :                 libMesh::out << "Starting solve n=" << n << ", q_l=" << q_l
    2113           0 :                              << " in RBConstruction::compute_output_dual_innerprods() at "
    2114           0 :                              << Utility::get_timestamp() << std::endl;
    2115             : 
    2116             :               // Use the main linear solver here instead of the inner_product solver, since
    2117             :               // get_matrix_for_output_dual_solves() may not return the inner product matrix.
    2118         568 :               solve_for_matrix_and_rhs(*get_linear_solver(), get_matrix_for_output_dual_solves(), *rhs);
    2119             : 
    2120             :               // We possibly perform multiple solves here with the same matrix, hence
    2121             :               // set reuse_preconditioner(true) (and set it back to false again below
    2122             :               // at the end of this function).
    2123         568 :               linear_solver->reuse_preconditioner(true);
    2124             : 
    2125         568 :               if (assert_convergence)
    2126         568 :                 check_convergence(*get_linear_solver());
    2127             : 
    2128         568 :               if (!is_quiet())
    2129             :                 {
    2130           0 :                   libMesh::out << "Finished solve n=" << n << ", q_l=" << q_l
    2131           0 :                                << " in RBConstruction::compute_output_dual_innerprods() at "
    2132           0 :                                << Utility::get_timestamp() << std::endl;
    2133             : 
    2134           0 :                   libMesh::out << this->n_linear_iterations()
    2135           0 :                                << " iterations, final residual "
    2136           0 :                                << this->final_linear_residual() << std::endl;
    2137             :                 }
    2138             : 
    2139         600 :               *L_q_representor[q_l] = *solution;
    2140             :             }
    2141             : 
    2142          16 :           unsigned int q=0;
    2143        1136 :           for (unsigned int q_l1=0; q_l1<get_rb_theta_expansion().get_n_output_terms(n); q_l1++)
    2144             :             {
    2145         568 :               get_matrix_for_output_dual_solves().vector_mult(*inner_product_storage_vector, *L_q_representor[q_l1]);
    2146             : 
    2147        1136 :               for (unsigned int q_l2=q_l1; q_l2<get_rb_theta_expansion().get_n_output_terms(n); q_l2++)
    2148             :                 {
    2149         600 :                   output_dual_innerprods[n][q] = L_q_representor[q_l2]->dot(*inner_product_storage_vector);
    2150          64 :                   libMesh::out << "output_dual_innerprods[" << n << "][" << q << "] = " << output_dual_innerprods[n][q] << std::endl;
    2151             : 
    2152         568 :                   q++;
    2153             :                 }
    2154             :             }
    2155             :         }
    2156             : 
    2157             :       // We may not need to use linear_solver again (e.g. this would happen if we use
    2158             :       // extra_linear_solver for the truth_solves). As a result, let's clear linear_solver
    2159             :       // to release any memory it may be taking up. If we do need it again, it will
    2160             :       // be initialized when necessary.
    2161         142 :       linear_solver->clear();
    2162         142 :       linear_solver->reuse_preconditioner(false);
    2163             : 
    2164         142 :       output_dual_innerprods_computed = true;
    2165         134 :     }
    2166             : 
    2167         142 :   get_rb_evaluation().output_dual_innerprods = output_dual_innerprods;
    2168             : }
    2169             : 
    2170         284 : void RBConstruction::compute_Fq_representor_innerprods(bool compute_inner_products)
    2171             : {
    2172             : 
    2173             :   // Skip calculations if we've already computed the Fq_representors
    2174         284 :   if (!Fq_representor_innerprods_computed)
    2175             :     {
    2176             :       // Only log if we get to here
    2177           8 :       LOG_SCOPE("compute_Fq_representor_innerprods()", "RBConstruction");
    2178             : 
    2179        1278 :       for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    2180             :         {
    2181        1022 :           if (!Fq_representor[q_f])
    2182             :             {
    2183        1932 :               Fq_representor[q_f] = NumericVector<Number>::build(this->comm());
    2184        1022 :               Fq_representor[q_f]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    2185             :             }
    2186             : 
    2187          28 :           libmesh_assert(Fq_representor[q_f]->size()       == this->n_dofs()       &&
    2188             :                          Fq_representor[q_f]->local_size() == this->n_local_dofs() );
    2189             : 
    2190         994 :           rhs->zero();
    2191         994 :           rhs->add(1., *get_Fq(q_f));
    2192             : 
    2193         994 :           if (!is_quiet())
    2194           0 :             libMesh::out << "Starting solve q_f=" << q_f
    2195           0 :                          << " in RBConstruction::update_residual_terms() at "
    2196           0 :                          << Utility::get_timestamp() << std::endl;
    2197             : 
    2198        1022 :           solve_for_matrix_and_rhs(*inner_product_solver, *inner_product_matrix, *rhs);
    2199             : 
    2200         994 :           if (assert_convergence)
    2201         994 :             check_convergence(*inner_product_solver);
    2202             : 
    2203         994 :           if (!is_quiet())
    2204             :             {
    2205           0 :               libMesh::out << "Finished solve q_f=" << q_f
    2206           0 :                            << " in RBConstruction::update_residual_terms() at "
    2207           0 :                            << Utility::get_timestamp() << std::endl;
    2208             : 
    2209           0 :               libMesh::out << this->n_linear_iterations()
    2210           0 :                            << " iterations, final residual "
    2211           0 :                            << this->final_linear_residual() << std::endl;
    2212             :             }
    2213             : 
    2214        1050 :           *Fq_representor[q_f] = *solution;
    2215             :         }
    2216             : 
    2217         284 :       if (compute_inner_products)
    2218             :         {
    2219           8 :           unsigned int q=0;
    2220             : 
    2221        1278 :           for (unsigned int q_f1=0; q_f1<get_rb_theta_expansion().get_n_F_terms(); q_f1++)
    2222             :             {
    2223         994 :               get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *Fq_representor[q_f1]);
    2224             : 
    2225        4118 :               for (unsigned int q_f2=q_f1; q_f2<get_rb_theta_expansion().get_n_F_terms(); q_f2++)
    2226             :                 {
    2227        3212 :                   Fq_representor_innerprods[q] = inner_product_storage_vector->dot(*Fq_representor[q_f2]);
    2228             : 
    2229        3124 :                   q++;
    2230             :                 }
    2231             :             }
    2232             :         } // end if (compute_inner_products)
    2233             : 
    2234         284 :       Fq_representor_innerprods_computed = true;
    2235             :     }
    2236             : 
    2237         284 :   get_rb_evaluation().Fq_representor_innerprods = Fq_representor_innerprods;
    2238         284 : }
    2239             : 
    2240         214 : void RBConstruction::load_rb_solution()
    2241             : {
    2242          12 :   LOG_SCOPE("load_rb_solution()", "RBConstruction");
    2243             : 
    2244         214 :   solution->zero();
    2245             : 
    2246         214 :   libmesh_error_msg_if(get_rb_evaluation().RB_solution.size() > get_rb_evaluation().get_n_basis_functions(),
    2247             :                        "ERROR: System contains " << get_rb_evaluation().get_n_basis_functions() << " basis functions."
    2248             :                        << " RB_solution vector contains " << get_rb_evaluation().RB_solution.size() << " entries."
    2249             :                        << " RB_solution in RBConstruction::load_rb_solution is too long!");
    2250             : 
    2251        3776 :   for (auto i : make_range(get_rb_evaluation().RB_solution.size()))
    2252        3556 :     solution->add(get_rb_evaluation().RB_solution(i), get_rb_evaluation().get_basis_function(i));
    2253             : 
    2254         214 :   update();
    2255         214 : }
    2256             : 
    2257             : // The slow (but simple, non-error prone) way to compute the residual dual norm
    2258             : // Useful for error checking
    2259           0 : Real RBConstruction::compute_residual_dual_norm_slow(const unsigned int N)
    2260             : {
    2261           0 :   LOG_SCOPE("compute_residual_dual_norm_slow()", "RBConstruction");
    2262             : 
    2263             :   // Put the residual in rhs in order to compute the norm of the Riesz representor
    2264             :   // Note that this only works in serial since otherwise each processor will
    2265             :   // have a different parameter value during the Greedy training.
    2266             : 
    2267           0 :   std::unique_ptr<NumericVector<Number>> RB_sol = NumericVector<Number>::build(comm());
    2268           0 :   RB_sol->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    2269             : 
    2270           0 :   std::unique_ptr<NumericVector<Number>> temp = NumericVector<Number>::build(comm());
    2271           0 :   temp->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    2272             : 
    2273           0 :   if (store_untransformed_basis)
    2274             :     {
    2275           0 :       libmesh_assert_equal_to(_untransformed_basis_functions.size(), get_rb_evaluation().get_n_basis_functions());
    2276             :     }
    2277             : 
    2278           0 :   for (unsigned int i=0; i<N; i++)
    2279             :   {
    2280           0 :     if (!store_untransformed_basis)
    2281             :       {
    2282           0 :         RB_sol->add(get_rb_evaluation().RB_solution(i), get_rb_evaluation().get_basis_function(i));
    2283             :       }
    2284             :     else
    2285             :       {
    2286           0 :         RB_sol->add(get_rb_evaluation().RB_solution(i), *_untransformed_basis_functions[i]);
    2287             :       }
    2288             :   }
    2289             : 
    2290           0 :   this->truth_assembly();
    2291           0 :   matrix->vector_mult(*temp, *RB_sol);
    2292           0 :   rhs->add(-1., *temp);
    2293             : 
    2294             :   // Then solve to get the Reisz representor
    2295           0 :   matrix->zero();
    2296           0 :   matrix->add(1., *inner_product_matrix);
    2297             : 
    2298           0 :   solve_for_matrix_and_rhs(*inner_product_solver, *inner_product_matrix, *rhs);
    2299           0 :   get_non_dirichlet_inner_product_matrix_if_avail()->vector_mult(*inner_product_storage_vector, *solution);
    2300           0 :   Number slow_residual_norm_sq = solution->dot(*inner_product_storage_vector);
    2301             : 
    2302           0 :   return std::sqrt( libmesh_real(slow_residual_norm_sq) );
    2303           0 : }
    2304             : 
    2305     1059389 : SparseMatrix<Number> * RBConstruction::get_inner_product_matrix()
    2306             : {
    2307     1059389 :   return inner_product_matrix.get();
    2308             : }
    2309             : 
    2310       28400 : const SparseMatrix<Number> * RBConstruction::get_inner_product_matrix() const
    2311             : {
    2312       28400 :   return inner_product_matrix.get();
    2313             : }
    2314             : 
    2315           0 : SparseMatrix<Number> * RBConstruction::get_non_dirichlet_inner_product_matrix()
    2316             : {
    2317           0 :   libmesh_error_msg_if(!store_non_dirichlet_operators,
    2318             :                        "Error: Must have store_non_dirichlet_operators==true to access non_dirichlet_inner_product_matrix.");
    2319             : 
    2320           0 :   return non_dirichlet_inner_product_matrix.get();
    2321             : }
    2322             : 
    2323           0 : const SparseMatrix<Number> * RBConstruction::get_non_dirichlet_inner_product_matrix() const
    2324             : {
    2325           0 :   libmesh_error_msg_if(!store_non_dirichlet_operators,
    2326             :                        "Error: Must have store_non_dirichlet_operators==true to access non_dirichlet_inner_product_matrix.");
    2327             : 
    2328           0 :   return non_dirichlet_inner_product_matrix.get();
    2329             : }
    2330             : 
    2331     1059388 : SparseMatrix<Number> * RBConstruction::get_non_dirichlet_inner_product_matrix_if_avail()
    2332             : {
    2333     1059388 :   if (store_non_dirichlet_operators)
    2334             :     {
    2335           0 :       return get_non_dirichlet_inner_product_matrix();
    2336             :     }
    2337             : 
    2338     1059388 :   return get_inner_product_matrix();
    2339             : }
    2340             : 
    2341       28400 : const SparseMatrix<Number> * RBConstruction::get_non_dirichlet_inner_product_matrix_if_avail() const
    2342             : {
    2343       28400 :   if (store_non_dirichlet_operators)
    2344             :     {
    2345           0 :       return get_non_dirichlet_inner_product_matrix();
    2346             :     }
    2347             : 
    2348       28400 :   return get_inner_product_matrix();
    2349             : }
    2350             : 
    2351     1130697 : SparseMatrix<Number> * RBConstruction::get_Aq(unsigned int q)
    2352             : {
    2353     1130697 :   libmesh_error_msg_if(q >= get_rb_theta_expansion().get_n_A_terms(),
    2354             :                        "Error: We must have q < Q_a in get_Aq.");
    2355             : 
    2356     1162917 :   return Aq_vector[q].get();
    2357             : }
    2358             : 
    2359           0 : SparseMatrix<Number> * RBConstruction::get_non_dirichlet_Aq(unsigned int q)
    2360             : {
    2361           0 :   libmesh_error_msg_if(!store_non_dirichlet_operators,
    2362             :                        "Error: Must have store_non_dirichlet_operators==true to access non_dirichlet_Aq.");
    2363             : 
    2364           0 :   libmesh_error_msg_if(q >= get_rb_theta_expansion().get_n_A_terms(),
    2365             :                        "Error: We must have q < Q_a in get_Aq.");
    2366             : 
    2367           0 :   return non_dirichlet_Aq_vector[q].get();
    2368             : }
    2369             : 
    2370      265158 : SparseMatrix<Number> * RBConstruction::get_non_dirichlet_Aq_if_avail(unsigned int q)
    2371             : {
    2372      265158 :   if (store_non_dirichlet_operators)
    2373             :     {
    2374           0 :       return get_non_dirichlet_Aq(q);
    2375             :     }
    2376             : 
    2377      265158 :   return get_Aq(q);
    2378             : }
    2379             : 
    2380      171800 : NumericVector<Number> * RBConstruction::get_Fq(unsigned int q)
    2381             : {
    2382      171800 :   libmesh_error_msg_if(q >= get_rb_theta_expansion().get_n_F_terms(),
    2383             :                        "Error: We must have q < Q_f in get_Fq.");
    2384             : 
    2385      176696 :   return Fq_vector[q].get();
    2386             : }
    2387             : 
    2388           0 : NumericVector<Number> * RBConstruction::get_non_dirichlet_Fq(unsigned int q)
    2389             : {
    2390           0 :   libmesh_error_msg_if(!store_non_dirichlet_operators,
    2391             :                        "Error: Must have store_non_dirichlet_operators==true to access non_dirichlet_Fq.");
    2392             : 
    2393           0 :   libmesh_error_msg_if(q >= get_rb_theta_expansion().get_n_F_terms(),
    2394             :                        "Error: We must have q < Q_f in get_Fq.");
    2395             : 
    2396           0 :   return non_dirichlet_Fq_vector[q].get();
    2397             : }
    2398             : 
    2399       15606 : NumericVector<Number> * RBConstruction::get_non_dirichlet_Fq_if_avail(unsigned int q)
    2400             : {
    2401       15606 :   if (store_non_dirichlet_operators)
    2402             :     {
    2403           0 :       return get_non_dirichlet_Fq(q);
    2404             :     }
    2405             : 
    2406       15606 :   return get_Fq(q);
    2407             : }
    2408             : 
    2409      584312 : NumericVector<Number> * RBConstruction::get_output_vector(unsigned int n, unsigned int q_l)
    2410             : {
    2411      584312 :   libmesh_error_msg_if((n >= get_rb_theta_expansion().get_n_outputs()) ||
    2412             :                        (q_l >= get_rb_theta_expansion().get_n_output_terms(n)),
    2413             :                        "Error: We must have n < n_outputs and "
    2414             :                        "q_l < get_rb_theta_expansion().get_n_output_terms(n) in get_output_vector.");
    2415             : 
    2416      617688 :   return outputs_vector[n][q_l].get();
    2417             : }
    2418             : 
    2419           0 : NumericVector<Number> * RBConstruction::get_non_dirichlet_output_vector(unsigned int n, unsigned int q_l)
    2420             : {
    2421           0 :   libmesh_error_msg_if((n >= get_rb_theta_expansion().get_n_outputs()) ||
    2422             :                        (q_l >= get_rb_theta_expansion().get_n_output_terms(n)),
    2423             :                        "Error: We must have n < n_outputs and "
    2424             :                        "q_l < get_rb_theta_expansion().get_n_output_terms(n) in get_non_dirichlet_output_vector.");
    2425             : 
    2426           0 :   return non_dirichlet_outputs_vector[n][q_l].get();
    2427             : }
    2428             : 
    2429           0 : void RBConstruction::get_all_matrices(std::map<std::string, SparseMatrix<Number> *> & all_matrices)
    2430             : {
    2431           0 :   all_matrices.clear();
    2432             : 
    2433           0 :   all_matrices["inner_product"] = get_inner_product_matrix();
    2434             : 
    2435           0 :   if (store_non_dirichlet_operators)
    2436             :     {
    2437           0 :       all_matrices["non_dirichlet_inner_product"] = get_non_dirichlet_inner_product_matrix();
    2438             :     }
    2439             : 
    2440           0 :   for (unsigned int q_a=0; q_a<get_rb_theta_expansion().get_n_A_terms(); q_a++)
    2441             :     {
    2442           0 :       std::stringstream matrix_name;
    2443           0 :       matrix_name << "A" << q_a;
    2444           0 :       all_matrices[matrix_name.str()] = get_Aq(q_a);
    2445             : 
    2446           0 :       if (store_non_dirichlet_operators)
    2447             :         {
    2448           0 :           matrix_name << "_non_dirichlet";
    2449           0 :           all_matrices[matrix_name.str()] = get_non_dirichlet_Aq(q_a);
    2450             :         }
    2451           0 :     }
    2452           0 : }
    2453             : 
    2454           0 : void RBConstruction::get_all_vectors(std::map<std::string, NumericVector<Number> *> & all_vectors)
    2455             : {
    2456           0 :   all_vectors.clear();
    2457             : 
    2458           0 :   get_output_vectors(all_vectors);
    2459             : 
    2460           0 :   for (unsigned int q_f=0; q_f<get_rb_theta_expansion().get_n_F_terms(); q_f++)
    2461             :     {
    2462           0 :       std::stringstream F_vector_name;
    2463           0 :       F_vector_name << "F" << q_f;
    2464           0 :       all_vectors[F_vector_name.str()] = get_Fq(q_f);
    2465             : 
    2466           0 :       if (store_non_dirichlet_operators)
    2467             :         {
    2468           0 :           F_vector_name << "_non_dirichlet";
    2469           0 :           all_vectors[F_vector_name.str()] = get_non_dirichlet_Fq(q_f);
    2470             :         }
    2471           0 :     }
    2472           0 : }
    2473             : 
    2474           0 : void RBConstruction::get_output_vectors(std::map<std::string, NumericVector<Number> *> & output_vectors)
    2475             : {
    2476           0 :   output_vectors.clear();
    2477             : 
    2478           0 :   for (unsigned int n=0; n<get_rb_theta_expansion().get_n_outputs(); n++)
    2479           0 :     for (unsigned int q_l=0; q_l<get_rb_theta_expansion().get_n_output_terms(n); q_l++)
    2480             :       {
    2481           0 :         std::stringstream output_name;
    2482           0 :         output_name << "output_" << n << "_"<< q_l;
    2483           0 :         output_vectors[output_name.str()] = get_output_vector(n,q_l);
    2484             : 
    2485           0 :         if (store_non_dirichlet_operators)
    2486             :           {
    2487           0 :             output_name << "_non_dirichlet";
    2488           0 :             output_vectors[output_name.str()] = get_non_dirichlet_output_vector(n,q_l);
    2489             :           }
    2490           0 :       }
    2491           0 : }
    2492             : 
    2493             : #ifdef LIBMESH_ENABLE_DIRICHLET
    2494             : 
    2495         568 : std::unique_ptr<DirichletBoundary> RBConstruction::build_zero_dirichlet_boundary_object()
    2496             : {
    2497          32 :   ZeroFunction<> zf;
    2498             : 
    2499          32 :   std::set<boundary_id_type> dirichlet_ids;
    2500          16 :   std::vector<unsigned int> variables;
    2501             : 
    2502             :   // The DirichletBoundary constructor clones zf, so it's OK that zf is only in local scope
    2503        1136 :   return std::make_unique<DirichletBoundary>(dirichlet_ids, variables, &zf);
    2504             : }
    2505             : 
    2506             : #endif
    2507             : 
    2508           0 : void RBConstruction::write_riesz_representors_to_files(const std::string & riesz_representors_dir,
    2509             :                                                        const bool write_binary_residual_representors)
    2510             : {
    2511           0 :   LOG_SCOPE("write_riesz_representors_to_files()", "RBConstruction");
    2512             : 
    2513             :   // Write out Riesz representors. These are useful to have when restarting,
    2514             :   // so you don't have to recompute them all over again.
    2515             : 
    2516             :   // First we write out the Fq representors, these are independent of an RBEvaluation object.
    2517           0 :   libMesh::out << "Writing out the Fq_representors..." << std::endl;
    2518             : 
    2519           0 :   std::ostringstream file_name;
    2520           0 :   const std::string riesz_representor_suffix = (write_binary_residual_representors ? ".xdr" : ".dat");
    2521             :   struct stat stat_info;
    2522             : 
    2523             :   // Residual representors written out to their own separate directory
    2524           0 :   if ( this->processor_id() == 0)
    2525           0 :     if ( Utility::mkdir(riesz_representors_dir.c_str()) != 0)
    2526           0 :       libMesh::out << "Skipping creating residual_representors directory: " << strerror(errno) << std::endl;
    2527             : 
    2528           0 :   for (auto i : index_range(Fq_representor))
    2529             :     {
    2530           0 :       if (Fq_representor[i])
    2531             :         {
    2532           0 :           file_name.str(""); // reset filename
    2533           0 :           file_name << riesz_representors_dir << "/Fq_representor" << i << riesz_representor_suffix;
    2534             : 
    2535             :           // Check to see if file exists, if so, don't overwrite it, we assume it was
    2536             :           // there from a previous call to this function.  Note: if stat returns zero
    2537             :           // it means it successfully got the file attributes (and therefore the file
    2538             :           // exists).  Because of the following factors:
    2539             :           // 1.) write_serialized_data takes longer for proc 0 than it does for others,
    2540             :           //     so processors can get out of sync.
    2541             :           // 2.) The constructor for Xdr opens a (0 length) file on *all* processors,
    2542             :           // there are typically hundreds of 0-length files created during this loop,
    2543             :           // and that screws up checking for the existence of files.  One way to stay
    2544             :           // in sync is to but a barrier at each iteration of the loop -- not sure how
    2545             :           // bad this will affect performance, but it can't be much worse than serialized
    2546             :           // I/O already is :)
    2547           0 :           int stat_result = stat(file_name.str().c_str(), &stat_info);
    2548             : 
    2549           0 :           if ( (stat_result != 0) ||     // file definitely doesn't already exist
    2550           0 :                (stat_info.st_size == 0)) // file exists, but has zero length (can happen if another proc already opened it!)
    2551             :             {
    2552             :               // No need to copy!
    2553             :               // *solution = *(Fq_representor[i]);
    2554             :               // std::swap doesn't work on pointers
    2555             :               //std::swap(solution.get(), Fq_representor[i]);
    2556           0 :               Fq_representor[i]->swap(*solution);
    2557             : 
    2558           0 :               Xdr fqr_data(file_name.str(),
    2559           0 :                            write_binary_residual_representors ? ENCODE : WRITE);
    2560             : 
    2561           0 :               write_serialized_data(fqr_data, false);
    2562             : 
    2563             :               // Synchronize before moving on
    2564           0 :               this->comm().barrier();
    2565             : 
    2566             :               // Swap back.
    2567           0 :               Fq_representor[i]->swap(*solution);
    2568             : 
    2569             :               // TODO: bzip the resulting file?  See $LIBMESH_DIR/src/mesh/unstructured_mesh.C
    2570             :               // for the system call, be sure to do it only on one processor, etc.
    2571           0 :             }
    2572             :         }
    2573             :     }
    2574             : 
    2575             : 
    2576             :   // Next, write out the Aq representors associated with rb_eval.
    2577           0 :   libMesh::out << "Writing out the Aq_representors..." << std::endl;
    2578             : 
    2579           0 :   const unsigned int jstop  = get_rb_evaluation().get_n_basis_functions();
    2580           0 :   const unsigned int jstart = jstop-get_delta_N();
    2581             : 
    2582           0 :   RBEvaluation & rbe = get_rb_evaluation();
    2583           0 :   for (auto i : index_range(rbe.Aq_representor))
    2584           0 :     for (unsigned int j=jstart; j<jstop; ++j)
    2585             :       {
    2586           0 :         libMesh::out << "Writing out Aq_representor[" << i << "][" << j << "]..." << std::endl;
    2587           0 :         libmesh_assert(get_rb_evaluation().Aq_representor[i][j]);
    2588             : 
    2589           0 :         file_name.str(""); // reset filename
    2590             :         file_name << riesz_representors_dir
    2591           0 :                   << "/Aq_representor" << i << "_" << j << riesz_representor_suffix;
    2592             : 
    2593             :         {
    2594             :           // No need to copy! Use swap instead.
    2595             :           // *solution = *(Aq_representor[i][j]);
    2596           0 :           rbe.Aq_representor[i][j]->swap(*solution);
    2597             : 
    2598           0 :           Xdr aqr_data(file_name.str(),
    2599           0 :                        write_binary_residual_representors ? ENCODE : WRITE);
    2600             : 
    2601           0 :           write_serialized_data(aqr_data, false);
    2602             : 
    2603             :           // Synchronize before moving on
    2604           0 :           this->comm().barrier();
    2605             : 
    2606             :           // Swap back.
    2607           0 :           rbe.Aq_representor[i][j]->swap(*solution);
    2608             : 
    2609             :           // TODO: bzip the resulting file?  See $LIBMESH_DIR/src/mesh/unstructured_mesh.C
    2610             :           // for the system call, be sure to do it only on one processor, etc.
    2611           0 :         }
    2612             :       }
    2613           0 : }
    2614             : 
    2615             : 
    2616             : 
    2617           0 : void RBConstruction::read_riesz_representors_from_files(const std::string & riesz_representors_dir,
    2618             :                                                         const bool read_binary_residual_representors)
    2619             : {
    2620           0 :   LOG_SCOPE("read_riesz_representors_from_files()", "RBConstruction");
    2621             : 
    2622           0 :   libMesh::out << "Reading in the Fq_representors..." << std::endl;
    2623             : 
    2624           0 :   const std::string riesz_representor_suffix = (read_binary_residual_representors ? ".xdr" : ".dat");
    2625           0 :   std::ostringstream file_name;
    2626             :   struct stat stat_info;
    2627             : 
    2628             :   // Read in the Fq_representors.  There should be Q_f of these.  FIXME:
    2629             :   // should we be worried about leaks here?
    2630           0 :   for (const auto & rep : Fq_representor)
    2631           0 :     libmesh_error_msg_if(rep, "Error, must delete existing Fq_representor before reading in from file.");
    2632             : 
    2633           0 :   for (auto i : index_range(Fq_representor))
    2634             :     {
    2635           0 :       file_name.str(""); // reset filename
    2636             :       file_name << riesz_representors_dir
    2637           0 :                 << "/Fq_representor" << i << riesz_representor_suffix;
    2638             : 
    2639             :       // On processor zero check to be sure the file exists
    2640           0 :       if (this->processor_id() == 0)
    2641             :         {
    2642           0 :           int stat_result = stat(file_name.str().c_str(), &stat_info);
    2643             : 
    2644           0 :           libmesh_error_msg_if(stat_result != 0, "File does not exist: " << file_name.str());
    2645             :         }
    2646             : 
    2647           0 :       Xdr fqr_data(file_name.str(),
    2648           0 :                    read_binary_residual_representors ? DECODE : READ);
    2649             : 
    2650           0 :       read_serialized_data(fqr_data, false);
    2651             : 
    2652           0 :       Fq_representor[i] = NumericVector<Number>::build(this->comm());
    2653           0 :       Fq_representor[i]->init (this->n_dofs(), this->n_local_dofs(), false, PARALLEL);
    2654             : 
    2655             :       // No need to copy, just swap
    2656             :       // *Fq_representor[i] = *solution;
    2657           0 :       Fq_representor[i]->swap(*solution);
    2658           0 :     }
    2659             : 
    2660             :   // Alert the update_residual_terms() function that we don't need to recompute
    2661             :   // the Fq_representors as we have already read them in from file!
    2662           0 :   Fq_representor_innerprods_computed = true;
    2663             : 
    2664             : 
    2665           0 :   libMesh::out << "Reading in the Aq_representors..." << std::endl;
    2666             : 
    2667             :   // Read in the Aq representors.  The class makes room for [Q_a][Nmax] of these.  We are going to
    2668             :   // read in [Q_a][get_rb_evaluation().get_n_basis_functions()].  FIXME:
    2669             :   // should we be worried about leaks in the locations where we're about to fill entries?
    2670           0 :   RBEvaluation & rbe = get_rb_evaluation();
    2671           0 :   for (const auto & row : rbe.Aq_representor)
    2672           0 :     for (const auto & rep : row)
    2673           0 :       libmesh_error_msg_if(rep, "Error, must delete existing Aq_representor before reading in from file.");
    2674             : 
    2675             :   // Now ready to read them in from file!
    2676           0 :   for (auto i : index_range(rbe.Aq_representor))
    2677           0 :     for (unsigned int j=0; j<rbe.get_n_basis_functions(); ++j)
    2678             :       {
    2679           0 :         file_name.str(""); // reset filename
    2680             :         file_name << riesz_representors_dir
    2681           0 :                   << "/Aq_representor" << i << "_" << j << riesz_representor_suffix;
    2682             : 
    2683             :         // On processor zero check to be sure the file exists
    2684           0 :         if (this->processor_id() == 0)
    2685             :           {
    2686           0 :             int stat_result = stat(file_name.str().c_str(), &stat_info);
    2687             : 
    2688           0 :             libmesh_error_msg_if(stat_result != 0, "File does not exist: " << file_name.str());
    2689             :           }
    2690             : 
    2691           0 :         Xdr aqr_data(file_name.str(), read_binary_residual_representors ? DECODE : READ);
    2692             : 
    2693           0 :         read_serialized_data(aqr_data, false);
    2694             : 
    2695           0 :         rbe.Aq_representor[i][j] = NumericVector<Number>::build(this->comm());
    2696           0 :         rbe.Aq_representor[i][j]->init (n_dofs(), n_local_dofs(),
    2697           0 :                                             false, PARALLEL);
    2698             : 
    2699             :         // No need to copy, just swap
    2700           0 :         rbe.Aq_representor[i][j]->swap(*solution);
    2701           0 :       }
    2702           0 : }
    2703             : 
    2704      160467 : void RBConstruction::check_convergence(LinearSolver<Number> & input_solver)
    2705             : {
    2706             :   libMesh::LinearConvergenceReason conv_flag;
    2707             : 
    2708      160467 :   conv_flag = input_solver.get_converged_reason();
    2709             : 
    2710      160467 :   libmesh_error_msg_if(conv_flag < 0, "Convergence error. Error id: " << conv_flag);
    2711      160467 : }
    2712             : 
    2713           0 : bool RBConstruction::get_convergence_assertion_flag() const
    2714             : {
    2715           0 :   return assert_convergence;
    2716             : }
    2717             : 
    2718           0 : void RBConstruction::set_convergence_assertion_flag(bool flag)
    2719             : {
    2720           0 :   assert_convergence = flag;
    2721           0 : }
    2722             : 
    2723      813684 : bool RBConstruction::get_preevaluate_thetas_flag() const
    2724             : {
    2725      813684 :   return _preevaluate_thetas_flag;
    2726             : }
    2727             : 
    2728           0 : void RBConstruction::set_preevaluate_thetas_flag(bool flag)
    2729             : {
    2730           0 :   _preevaluate_thetas_flag = flag;
    2731           0 : }
    2732             : 
    2733           0 : unsigned int RBConstruction::get_current_training_parameter_index() const
    2734             : {
    2735           0 :   return _current_training_parameter_index;
    2736             : }
    2737             : 
    2738           0 : void RBConstruction::set_current_training_parameter_index(unsigned int index)
    2739             : {
    2740           0 :   _current_training_parameter_index = index;
    2741           0 : }
    2742             : 
    2743             : const std::vector<Number> &
    2744           0 : RBConstruction::get_evaluated_thetas(unsigned int training_parameter_index) const
    2745             : {
    2746           0 :   const numeric_index_type first_index = get_first_local_training_index();
    2747           0 :   libmesh_assert(training_parameter_index >= first_index);
    2748             : 
    2749           0 :   const numeric_index_type local_index = training_parameter_index - first_index;
    2750           0 :   libmesh_assert(local_index < _evaluated_thetas.size());
    2751             : 
    2752           0 :   return _evaluated_thetas[local_index];
    2753             : }
    2754             : 
    2755           0 : void RBConstruction::preevaluate_thetas()
    2756             : {
    2757           0 :   LOG_SCOPE("preevaluate_thetas()", "RBConstruction");
    2758             : 
    2759           0 :   _evaluated_thetas.resize(get_local_n_training_samples());
    2760             : 
    2761             :   // Early return if we've already preevaluated thetas.
    2762           0 :   if (_preevaluate_thetas_completed)
    2763           0 :     return;
    2764             : 
    2765           0 :   if ( get_local_n_training_samples() == 0 )
    2766           0 :     return;
    2767             : 
    2768           0 :   auto & rb_theta_expansion = get_rb_evaluation().get_rb_theta_expansion();
    2769           0 :   const unsigned int n_A_terms = rb_theta_expansion.get_n_A_terms();
    2770           0 :   const unsigned int n_F_terms = rb_theta_expansion.get_n_F_terms();
    2771           0 :   const unsigned int n_outputs = rb_theta_expansion.get_total_n_output_terms();
    2772             : 
    2773             :   // Collect all training parameters
    2774             :   // TODO: Here instead of using a vector of RBParameters objects,
    2775             :   // we could use a single RBParameters object with multiple samples.
    2776             :   // This would save memory over the current approach, but that may
    2777             :   // not be a big deal in practice unless the number of training samples
    2778             :   // is very large for some reason.
    2779           0 :   std::vector<RBParameters> mus(get_local_n_training_samples());
    2780           0 :   const numeric_index_type first_index = get_first_local_training_index();
    2781           0 :   for (unsigned int i=0; i<get_local_n_training_samples(); i++)
    2782             :     {
    2783             :       // Load training parameter i, this is only loaded
    2784             :       // locally since the RB solves are local.
    2785           0 :       set_params_from_training_set( first_index+i );
    2786           0 :       mus[i] = get_parameters();
    2787           0 :       _evaluated_thetas[i].resize(n_A_terms + n_F_terms + n_outputs);
    2788             :     }
    2789             : 
    2790             :   // Evaluate thetas for all training parameters simultaneously
    2791           0 :   for (unsigned int q_a=0; q_a<n_A_terms; q_a++)
    2792             :     {
    2793           0 :       const auto A_vals = rb_theta_expansion.eval_A_theta(q_a, mus);
    2794           0 :       for (auto i : make_range(get_local_n_training_samples()))
    2795           0 :         _evaluated_thetas[i][q_a] = A_vals[i];
    2796             :     }
    2797             : 
    2798           0 :   for (unsigned int q_f=0; q_f<n_F_terms; q_f++)
    2799             :     {
    2800           0 :       const auto F_vals = rb_theta_expansion.eval_F_theta(q_f, mus);
    2801           0 :       for (auto i : make_range(get_local_n_training_samples()))
    2802           0 :         _evaluated_thetas[i][n_A_terms + q_f] = F_vals[i];
    2803             :     }
    2804             : 
    2805             :   {
    2806           0 :     unsigned int output_counter = 0;
    2807           0 :     for (unsigned int n=0; n<rb_theta_expansion.get_n_outputs(); n++)
    2808           0 :       for (unsigned int q_l=0; q_l<rb_theta_expansion.get_n_output_terms(n); q_l++)
    2809             :         {
    2810             :           // Evaluate the current output functional term for all
    2811             :           // training parameters simultaneously.
    2812           0 :           const auto output_vals = rb_theta_expansion.eval_output_theta(n, q_l, mus);
    2813             : 
    2814             :           // TODO: the size of _evaluated_thetas is currently assumed to be
    2815             :           // the same as get_local_n_training_samples(), but this won't be
    2816             :           // the case if we use RBParameters objects that have multiple samples.
    2817             :           // So just make sure that's the case for now.
    2818           0 :           libmesh_error_msg_if(output_vals.size() != get_local_n_training_samples(),
    2819             :                                "We currently only support single-sample RBParameters "
    2820             :                                "objects during the training stage.");
    2821             : 
    2822           0 :           for (auto i : index_range(output_vals))
    2823           0 :             _evaluated_thetas[i][n_A_terms + n_F_terms + output_counter] = output_vals[i];
    2824             : 
    2825             :           // Go to next output term
    2826           0 :           output_counter++;
    2827             :         }
    2828             :   }
    2829             : 
    2830           0 :   _preevaluate_thetas_completed = true;
    2831           0 : }
    2832             : 
    2833           0 : void RBConstruction::reset_preevaluate_thetas_completed()
    2834             : {
    2835           0 :   _preevaluate_thetas_completed = false;
    2836           0 : }
    2837             : 
    2838             : } // namespace libMesh

Generated by: LCOV version 1.14