LCOV - code coverage report
Current view: top level - src/reporters - MappingReporter.C (source / functions) Hit Total Coverage
Test: idaholab/moose stochastic_tools: f45d79 Lines: 67 70 95.7 %
Date: 2025-07-25 05:00:46 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          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 : }

Generated by: LCOV version 1.14