adamantine
ensemble_management.cc
Go to the documentation of this file.
1 /* SPDX-FileCopyrightText: Copyright (c) 2021-2025, the adamantine authors.
2  * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3  */
4 
5 #include <CubeHeatSource.hh>
7 #include <GoldakHeatSource.hh>
8 #include <ensemble_management.hh>
9 #include <utils.hh>
10 
11 #include <fstream>
12 #include <random>
13 
14 namespace adamantine
15 {
16 namespace
17 {
18 std::vector<double> get_normal_random_vector(unsigned int length,
19  unsigned int n_rejected_draws,
20  double mean, double stddev,
21  bool verbose)
22 {
23  ASSERT(stddev >= 0., "Internal Error");
24 
25  std::mt19937 pseudorandom_number_generator;
26  std::normal_distribution<> normal_dist_generator(mean, stddev);
27  for (unsigned int i = 0; i < n_rejected_draws; ++i)
28  {
29  normal_dist_generator(pseudorandom_number_generator);
30  }
31 
32  std::vector<double> output_vector(length);
33  for (unsigned int i = 0; i < length; ++i)
34  {
35  // We reject negative values because physical quantities we care about are
36  // all positive and we cannot guarantee that the normal distribution will
37  // always be positive.
38  do
39  {
40  output_vector[i] = normal_dist_generator(pseudorandom_number_generator);
41 
42  if (verbose && output_vector[i] < 0.)
43  {
44  std::cout << "Random value rejected because it was negative: "
45  << output_vector[i] << std::endl;
46  }
47 
48  } while (output_vector[i] < 0.);
49  }
50 
51  return output_vector;
52 }
53 } // namespace
54 
55 template <int dim>
56 std::vector<std::shared_ptr<HeatSource<dim>>> get_bounding_heat_sources(
57  std::vector<boost::property_tree::ptree> const &database_ensemble,
58  MPI_Comm global_communicator)
59 {
60  // To bound the volumes of the Goldak and electron beam sources, we just need
61  // to know their diameter and their depths. This is not enough for the cube
62  // source but because the source is static and only use for testing, we just
63  // assume that the source is identical for all ensemble members.
64  // Use a std::vector instead of std::pair so deal.II can serialize the object.
65  std::vector<std::vector<std::vector<double>>> diameter_depth_ensemble;
66  for (auto const &database : database_ensemble)
67  {
68  std::vector<std::vector<double>> diameter_depth_beams;
69  boost::property_tree::ptree const &source_database =
70  database.get_child("sources");
71  // PropertyTreeInput sources.n_beams
72  unsigned int const n_beams = source_database.get<unsigned int>("n_beams");
73  for (unsigned int i = 0; i < n_beams; ++i)
74  {
75  boost::property_tree::ptree const &beam_database =
76  source_database.get_child("beam_" + std::to_string(i));
77 
78  // PropertyTreeInput sources.beam_X.type
79  std::string type = beam_database.get<std::string>("type");
80  if (type != "cube")
81  {
82  // PropertyTreeInput sources.beam_X.diameter
83  double diameter = beam_database.get<double>("diameter");
84  // PropertyTreeInput sources.beam_X.depth
85  double depth = beam_database.get<double>("diameter");
86  diameter_depth_beams.push_back({diameter, depth});
87  }
88  }
89  diameter_depth_ensemble.push_back(diameter_depth_beams);
90  }
91 
92  // Use all_gather to communicate the diameters and the depths
93  auto all_diameter_depth = dealii::Utilities::MPI::all_gather(
94  global_communicator, diameter_depth_ensemble);
95 
96  // Compute the maximum diameter and depth
97  std::vector<double> diameter_max(diameter_depth_ensemble[0].size(), -1.);
98  std::vector<double> depth_max(diameter_depth_ensemble[0].size(), -1.);
99  for (auto const &diameter_depth_rank : all_diameter_depth)
100  {
101  for (unsigned int member = 0; member < diameter_depth_rank.size(); ++member)
102  {
103  for (unsigned int beam = 0; beam < diameter_depth_rank[member].size();
104  ++beam)
105  {
106  if (diameter_depth_rank[member][beam][0] > diameter_max[beam])
107  {
108  diameter_max[beam] = diameter_depth_rank[member][beam][0];
109  }
110  if (diameter_depth_rank[member][beam][1] > depth_max[beam])
111  {
112  depth_max[beam] = diameter_depth_rank[member][beam][1];
113  }
114  }
115  }
116  }
117 
118  // Get the units database
119  boost::optional<boost::property_tree::ptree const &> units_optional_database =
120  database_ensemble[0].get_child_optional("units");
121 
122  // Create the bounding sources
123  boost::property_tree::ptree const &source_database =
124  database_ensemble[0].get_child("sources");
125  // PropertyTreeInput sources.n_beams
126  unsigned int const n_beams = source_database.get<unsigned int>("n_beams");
127  std::vector<std::shared_ptr<HeatSource<dim>>> bounding_heat_sources(n_beams);
128  unsigned int bounding_source = 0;
129  for (unsigned int i = 0; i < n_beams; ++i)
130  {
131  boost::property_tree::ptree const &beam_database =
132  source_database.get_child("beam_" + std::to_string(i));
133 
134  // PropertyTreeInput sources.beam_X.type
135  std::string type = beam_database.get<std::string>("type");
136  if (type == "goldak")
137  {
138  boost::property_tree::ptree modified_beam_database = beam_database;
139  modified_beam_database.put("diameter", diameter_max[bounding_source]);
140  modified_beam_database.put("depth", depth_max[bounding_source]);
141  ++bounding_source;
142 
143  bounding_heat_sources[i] = std::make_shared<GoldakHeatSource<dim>>(
144  modified_beam_database, units_optional_database);
145  }
146  else if (type == "electron_beam")
147  {
148  boost::property_tree::ptree modified_beam_database = beam_database;
149  modified_beam_database.put("diameter", diameter_max[bounding_source]);
150  modified_beam_database.put("depth", depth_max[bounding_source]);
151  ++bounding_source;
152 
153  bounding_heat_sources[i] = std::make_shared<ElectronBeamHeatSource<dim>>(
154  modified_beam_database, units_optional_database);
155  }
156  else if (type == "cube")
157  {
158  bounding_heat_sources[i] = std::make_shared<CubeHeatSource<dim>>(
159  beam_database, units_optional_database);
160  }
161  }
162 
163  return bounding_heat_sources;
164 }
165 
166 void traverse(boost::property_tree::ptree const &ensemble_ptree,
167  std::vector<boost::property_tree::ptree> &database_ensemble,
168  std::vector<std::string> &keys, unsigned int &n_rejected_draws,
169  unsigned int local_ensemble_size,
170  unsigned int global_ensemble_size, std::string const &path = "")
171 {
172  for (auto const &[key, child] : ensemble_ptree)
173  {
174  std::string full_path = path.empty() ? key : path + "." + key;
175 
176  // If the child is empty, we have a full path
177  if (child.empty())
178  {
179  // Skip ensemble_simulation and ensemble_size
180  if ((full_path == "ensemble_simulation") ||
181  (full_path == "ensemble_size"))
182  {
183  continue;
184  }
185 
186  double stddev = database_ensemble[0].get<double>("ensemble." + full_path);
187  // The key in database_ensemble is full_path without the _stddev.
188  full_path.erase(full_path.length() - 7);
189  double mean = database_ensemble[0].get<double>(full_path);
190 
191  std::vector<double> values = get_normal_random_vector(
192  local_ensemble_size, n_rejected_draws, mean, stddev,
193  database_ensemble[0].get("verbose_output", false));
194  n_rejected_draws += global_ensemble_size;
195 
196  // Store full_path for when we write the value in a file
197  keys.push_back(full_path);
198 
199  // Update the database_ensemble
200  for (unsigned int member = 0; member < local_ensemble_size; ++member)
201  {
202  database_ensemble[member].put(full_path, values[member]);
203  }
204  }
205  else
206  {
207  traverse(child, database_ensemble, keys, n_rejected_draws,
208  local_ensemble_size, global_ensemble_size, full_path);
209  }
210  }
211 }
212 
213 std::vector<boost::property_tree::ptree> create_database_ensemble(
214  boost::property_tree::ptree const &database, MPI_Comm local_communicator,
215  unsigned int first_local_member, unsigned int local_ensemble_size,
216  unsigned int global_ensemble_size)
217 {
218  std::vector<boost::property_tree::ptree> database_ensemble(
219  local_ensemble_size, database);
220 
221  // The structure inside ensemble needs to match the rest of the database
222  // Do a try catch to get a nice error message other it's going to be
223  // strange once we erase the _stddev
224  std::vector<std::string> keys;
225  try
226  {
227  unsigned int n_rejected_draws = first_local_member;
228  traverse(database.get_child("ensemble"), database_ensemble, keys,
229  n_rejected_draws, local_ensemble_size, global_ensemble_size);
230  }
231  catch (boost::property_tree::ptree_bad_path &exception)
232  {
233  std::cerr << std::endl;
234  std::cerr << "Aborting in ensemble management." << std::endl;
235  std::cerr << "Error: " << exception.what() << std::endl << std::endl;
236  std::cerr << "There is a problem with the input file." << std::endl;
237  std::cerr << "Make sure that the keys in ensemble match" << std::endl;
238  std::cerr << "the keys in the rest of the input file." << std::endl;
239  std::cerr << "Note the keys must have been set explicitly" << std::endl;
240  std::cerr << "even if they normally have a default value." << std::endl;
241  std::cerr << std::endl;
242  std::abort();
243  }
244 
245  // Write the ensemble variables in files to simplify data analysis.
246  if (dealii::Utilities::MPI::this_mpi_process(local_communicator) == 0)
247  {
248  for (unsigned int member = 0; member < local_ensemble_size; ++member)
249  {
250  std::string output_dir =
251  database.get<std::string>("post_processor.output_dir", "");
252  std::string member_data_filename =
253  output_dir +
254  database.get<std::string>("post_processor.filename_prefix") + "_m" +
255  std::to_string(first_local_member + member) + "_data.txt";
256  std::ofstream file(member_data_filename);
257  for (auto const &k : keys)
258  {
259  file << k << ": " << database_ensemble[member].get<double>(k) << "\n";
260  }
261  }
262  }
263 
264  return database_ensemble;
265 }
266 } // namespace adamantine
267 
268 //-------------------- Explicit Instantiations --------------------//
269 namespace adamantine
270 {
271 template std::vector<std::shared_ptr<HeatSource<2>>> get_bounding_heat_sources(
272  std::vector<boost::property_tree::ptree> const &property_trees,
273  MPI_Comm global_communicator);
274 template std::vector<std::shared_ptr<HeatSource<3>>> get_bounding_heat_sources(
275  std::vector<boost::property_tree::ptree> const &property_trees,
276  MPI_Comm global_communicator);
277 } // namespace adamantine
void traverse(boost::property_tree::ptree const &ensemble_ptree, std::vector< boost::property_tree::ptree > &database_ensemble, std::vector< std::string > &keys, unsigned int &n_rejected_draws, unsigned int local_ensemble_size, unsigned int global_ensemble_size, std::string const &path="")
std::vector< boost::property_tree::ptree > create_database_ensemble(boost::property_tree::ptree const &database, MPI_Comm local_communicator, unsigned int first_local_member, unsigned int local_ensemble_size, unsigned int global_ensemble_size)
std::vector< std::shared_ptr< HeatSource< dim > > > get_bounding_heat_sources(std::vector< boost::property_tree::ptree > const &database_ensemble, MPI_Comm global_communicator)
#define ASSERT(condition, message)
Definition: utils.hh:68