Line data Source code
1 : //* This file is part of the MOOSE framework 2 : //* https://mooseframework.inl.gov 3 : //* 4 : //* All rights reserved, see COPYRIGHT for full restrictions 5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT 6 : //* 7 : //* Licensed under LGPL 2.1, please see LICENSE for details 8 : //* https://www.gnu.org/licenses/lgpl-2.1.html 9 : 10 : // Stocastic Tools Includes 11 : #include "MappingReporter.h" 12 : #include "NonlinearSystemBase.h" 13 : #include "libmesh/parallel_sync.h" 14 : #include "VectorPacker.h" 15 : 16 : #include "Sampler.h" 17 : 18 : registerMooseObject("StochasticToolsApp", MappingReporter); 19 : 20 : InputParameters 21 120 : MappingReporter::validParams() 22 : { 23 120 : InputParameters params = StochasticReporter::validParams(); 24 120 : params += SamplerInterface::validParams(); 25 120 : params += MappingInterface::validParams(); 26 120 : params.addClassDescription( 27 : "A reporter which can map full solution fields to a latent space for given variables."); 28 240 : params.addRequiredParam<UserObjectName>("mapping", "Name of the mapping object."); 29 240 : params.addRequiredParam<std::vector<VariableName>>( 30 : "variables", "The names of the variables which need to be mapped to the latent space."); 31 240 : params.addParam<std::string>("parallel_storage", 32 : "The storage space where the snapshots are stored. These snapshots " 33 : "are used to build the mapping. If this parameter is not specified, " 34 : "the reporter will fetch the variable from the nonlinear system."); 35 240 : params.addParam<SamplerName>( 36 : "sampler", 37 : "Sampler be able to identify how the samples are distributed among " 38 : "the processes. Only needed if parallel storage is defined. It is important to have the " 39 : "same sampler here as the one used to prepare the snapshots in the parallel storage."); 40 120 : return params; 41 0 : } 42 : 43 60 : MappingReporter::MappingReporter(const InputParameters & parameters) 44 : : StochasticReporter(parameters), 45 : MappingInterface(this), 46 180 : _parallel_storage(isParamValid("parallel_storage") 47 100 : ? &getUserObject<ParallelSolutionStorage>("parallel_storage") 48 : : nullptr), 49 160 : _sampler(isParamValid("sampler") ? &getSampler("sampler") : nullptr), 50 120 : _mapping_name(getParam<UserObjectName>("mapping")), 51 180 : _variable_names(getParam<std::vector<VariableName>>("variables")) 52 : { 53 60 : if (_parallel_storage) 54 : { 55 40 : if (_sampler) 56 : { 57 : // Declaring the reporters for every variable. This is a collection of vectors which describe 58 : // the coordinates of the solution fields in the latent space. 59 40 : _vector_real_values_parallel_storage.resize(_variable_names.size()); 60 120 : for (auto var_i : index_range(_variable_names)) 61 : { 62 80 : _vector_real_values_parallel_storage[var_i] = &declareStochasticReporter<std::vector<Real>>( 63 160 : _variable_names[var_i] + "_" + _mapping_name, *_sampler); 64 : } 65 : } 66 : else 67 0 : paramError("sampler", 68 : "We need a sampler object if the parallel storage is supplied! The sampler object " 69 : "shall be the same as the one used to generate the solution fields in the " 70 : "parallel storage object."); 71 : } 72 : else 73 : { 74 : // Declaring the reporters for every variable. This is a collection of vectors which describe 75 : // the coordinates of the solution fields in the latent space. 76 20 : _vector_real_values.resize(_variable_names.size()); 77 60 : for (auto var_i : index_range(_variable_names)) 78 120 : _vector_real_values[var_i] = &declareValueByName<std::vector<Real>>( 79 80 : _variable_names[var_i] + "_" + _mapping_name, REPORTER_MODE_ROOT); 80 : } 81 60 : } 82 : 83 : void 84 60 : MappingReporter::initialSetup() 85 : { 86 60 : _mapping = &getMappingByName(_mapping_name); 87 60 : } 88 : 89 : void 90 200 : MappingReporter::execute() 91 : { 92 : // We have two execution modes. If the parallel storage is supplied we loop over the snapshots in 93 : // the parallel storage, and project them to obtain their coefficients. 94 200 : if (_parallel_storage) 95 40 : mapParallelStorageData(); 96 : // The alternative option is to just fetch the variables and reduce them 97 : else 98 160 : mapVariableData(); 99 200 : } 100 : 101 : void 102 40 : MappingReporter::mapParallelStorageData() 103 : { 104 : // If the mapping is not built yet, we shall build it using the solutions in the parallel 105 : // storage. The conditional process is decided in the mapping object. 106 120 : for (const auto & var : _variable_names) 107 80 : _mapping->buildMapping(var); 108 : 109 : // Since the solution fields can be distributed among the processes of each sub-application 110 : // (unlike samples and reporter values which are just on the root process), we need to use the 111 : // information in the sampler to check which solution is where. 112 40 : const auto rank_config = _sampler->getRankConfig(true); 113 : 114 : // We need to do some parallel communication in case we have snapshots on processes other than 115 : // the roots of the sub-applications. This will collect the vectors that we need to send in the 116 : // following format: 117 : // <to which processor, (variable index, global sample index, solution field)> 118 : std::unordered_map<processor_id_type, 119 : std::vector<std::tuple<unsigned int, unsigned int, std::vector<Real>>>> 120 : send_map; 121 : 122 : // We need to use the rank config here to ensure we are looping on the non-root processors 123 : // too 124 360 : for (const auto sample_i : make_range(rank_config.num_local_sims)) 125 : { 126 320 : std::vector<Real> data = _sampler->getNextLocalRow(); 127 : 128 : // Converting the local indexing to global sample indices 129 320 : const unsigned int global_i = sample_i + _sampler->getLocalRowBegin(); 130 : 131 960 : for (const auto var_i : index_range(_variable_names)) 132 : { 133 : // Create a temporary storage for the coordinates in the latent space 134 : std::vector<Real> local_vector; 135 : 136 : // Check if the current process has this global sample 137 640 : if (_parallel_storage->hasGlobalSample(global_i, _variable_names[var_i])) 138 : { 139 : // Fetch the solution vector for the given sample index and variable 140 : const auto & full_vector = 141 320 : _parallel_storage->getGlobalSample(global_i, _variable_names[var_i]); 142 : 143 : // At the moment we only support simulations which have only one solution field 144 : // per sample. This is typically a steady-state simulation. 145 320 : if (full_vector.size() != 1) 146 0 : mooseError("MappingReporter is only supported for simulations with one solution " 147 : "field per run!"); 148 : 149 : // We use the mapping object to generate the coordinates in the latent space 150 320 : _mapping->map(_variable_names[var_i], global_i, local_vector); 151 : 152 : // If we are on the root processor of the sub-application, we simply insert the result 153 : // into the reporter storage space. Othervise we will send it to the root process. 154 320 : if (rank_config.is_first_local_rank) 155 160 : (*_vector_real_values_parallel_storage[var_i])[sample_i] = local_vector; 156 : else 157 160 : send_map[rank_config.my_first_rank].emplace_back( 158 : var_i, sample_i, std::move(local_vector)); 159 : } 160 : } 161 : } 162 : 163 : // This functor describes what we do when we receive the samples from other processes 164 : auto receive_functor = 165 20 : [this](processor_id_type /*pid*/, 166 160 : const std::vector<std::tuple<unsigned int, unsigned int, std::vector<Real>>> & vectors) 167 : { 168 : // We unpack the tuples and insert the values into the reporter 169 180 : for (const auto & [var_i, sample_i, vector] : vectors) 170 160 : (*_vector_real_values_parallel_storage[var_i])[sample_i] = std::move(vector); 171 60 : }; 172 : 173 : // We send the results from the non-root processors to the root processors 174 40 : Parallel::push_parallel_packed_range(_communicator, send_map, (void *)nullptr, receive_functor); 175 40 : } 176 : 177 : void 178 160 : MappingReporter::mapVariableData() 179 : { 180 160 : NonlinearSystemBase & nl = _fe_problem.getNonlinearSystemBase(/*nl_sys_num=*/0); 181 : 182 480 : for (unsigned int var_i = 0; var_i < _variable_names.size(); ++var_i) 183 : { 184 : // Getting the corresponding DoF indices for the variable. 185 320 : nl.setVariableGlobalDoFs(_variable_names[var_i]); 186 : 187 : // We need to serialize the solution on the root process first. 188 320 : DenseVector<Real> serialized_solution; 189 640 : nl.solution().localize(serialized_solution.get_values(), 190 640 : processor_id() == 0 ? nl.getVariableGlobalDoFs() 191 : : std::vector<dof_id_type>()); 192 : // We map the solution into the latent space and save the solutions in one go 193 320 : if (processor_id() == 0) 194 160 : _mapping->map(_variable_names[var_i], serialized_solution, *_vector_real_values[var_i]); 195 : } 196 160 : }