adamantine
adamantine.cc
Go to the documentation of this file.
1 /* SPDX-FileCopyrightText: Copyright (c) 2016 - 2025, the adamantine authors.
2  * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3  */
4 
5 #include "adamantine.hh"
6 
7 #include <MaterialStates.hh>
8 #include <instantiation.hh>
9 #include <utils.hh>
11 
12 #include <boost/program_options.hpp>
13 #include <boost/property_tree/info_parser.hpp>
14 #include <boost/property_tree/json_parser.hpp>
15 
16 #include <Kokkos_Core.hpp>
17 
18 #include <filesystem>
19 #include <tuple>
20 
21 #ifdef ADAMANTINE_WITH_ADIAK
22 #include <adiak.hpp>
23 #endif
24 
25 #ifdef ADAMANTINE_WITH_CALIPER
26 #include <caliper/cali-manager.h>
27 #endif
28 
29 // Define macros that replace nested ifs
30 #define RUN_ENSEMBLE_SOLID_HOST_IF(z, SEQ) \
31  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
32  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
33  BOOST_PP_SEQ_ELEM(2, SEQ), \
34  BOOST_PP_SEQ_ELEM(3, SEQ))) \
35  run_ensemble<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
36  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::Solid, \
37  dealii::MemorySpace::Host>(communicator, database, timers);
38 
39 #define RUN_ENSEMBLE_SOLID_HOST(TUPLE) \
40  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
41  RUN_ENSEMBLE_SOLID_HOST_IF, \
42  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
43 
44 #define RUN_ENSEMBLE_SOLID_LIQUID_HOST_IF(z, SEQ) \
45  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
46  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
47  BOOST_PP_SEQ_ELEM(2, SEQ), \
48  BOOST_PP_SEQ_ELEM(3, SEQ))) \
49  run_ensemble<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
50  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::SolidLiquid, \
51  dealii::MemorySpace::Host>(communicator, database, timers);
52 
53 #define RUN_ENSEMBLE_SOLID_LIQUID_HOST(TUPLE) \
54  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
55  RUN_ENSEMBLE_SOLID_LIQUID_HOST_IF, \
56  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
57 
58 #define RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST_IF(z, SEQ) \
59  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
60  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
61  BOOST_PP_SEQ_ELEM(2, SEQ), \
62  BOOST_PP_SEQ_ELEM(3, SEQ))) \
63  run_ensemble<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
64  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::SolidLiquidPowder, \
65  dealii::MemorySpace::Host>(communicator, database, timers);
66 
67 #define RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST(TUPLE) \
68  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
69  RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST_IF, \
70  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
71 
72 #define RUN_SOLID_HOST_IF(z, SEQ) \
73  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
74  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
75  BOOST_PP_SEQ_ELEM(2, SEQ), \
76  BOOST_PP_SEQ_ELEM(3, SEQ))) \
77  run<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
78  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::Solid, \
79  dealii::MemorySpace::Host>(communicator, database, timers);
80 
81 #define RUN_SOLID_HOST(TUPLE) \
82  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
83  RUN_SOLID_HOST_IF, \
84  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
85 
86 #define RUN_SOLID_LIQUID_HOST_IF(z, SEQ) \
87  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
88  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
89  BOOST_PP_SEQ_ELEM(2, SEQ), \
90  BOOST_PP_SEQ_ELEM(3, SEQ))) \
91  run<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
92  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::SolidLiquid, \
93  dealii::MemorySpace::Host>(communicator, database, timers);
94 
95 #define RUN_SOLID_LIQUID_HOST(TUPLE) \
96  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
97  RUN_SOLID_LIQUID_HOST_IF, \
98  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
99 
100 #define RUN_SOLID_LIQUID_POWDER_HOST_IF(z, SEQ) \
101  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
102  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
103  BOOST_PP_SEQ_ELEM(2, SEQ), \
104  BOOST_PP_SEQ_ELEM(3, SEQ))) \
105  run<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
106  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::SolidLiquidPowder, \
107  dealii::MemorySpace::Host>(communicator, database, timers);
108 
109 #define RUN_SOLID_LIQUID_POWDER_HOST(TUPLE) \
110  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
111  RUN_SOLID_LIQUID_POWDER_HOST_IF, \
112  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
113 
114 #define RUN_SOLID_DEV_IF(z, SEQ) \
115  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
116  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
117  BOOST_PP_SEQ_ELEM(2, SEQ), \
118  BOOST_PP_SEQ_ELEM(3, SEQ))) \
119  run<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
120  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::Solid, \
121  dealii::MemorySpace::Default>(communicator, database, timers);
122 
123 #define RUN_SOLID_DEVICE(TUPLE) \
124  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
125  RUN_SOLID_DEV_IF, \
126  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
127 
128 #define RUN_SOLID_LIQUID_DEV_IF(z, SEQ) \
129  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
130  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
131  BOOST_PP_SEQ_ELEM(2, SEQ), \
132  BOOST_PP_SEQ_ELEM(3, SEQ))) \
133  run<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
134  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::SolidLiquid, \
135  dealii::MemorySpace::Default>(communicator, database, timers);
136 
137 #define RUN_SOLID_LIQUID_DEVICE(TUPLE) \
138  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
139  RUN_SOLID_LIQUID_DEV_IF, \
140  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
141 
142 #define RUN_SOLID_LIQUID_POWDER_DEV_IF(z, SEQ) \
143  if (BOOST_PP_SEQ_ELEM(0, SEQ) == \
144  std::tuple<int, int, int>(BOOST_PP_SEQ_ELEM(1, SEQ), \
145  BOOST_PP_SEQ_ELEM(2, SEQ), \
146  BOOST_PP_SEQ_ELEM(3, SEQ))) \
147  run<BOOST_PP_SEQ_ELEM(1, SEQ), BOOST_PP_SEQ_ELEM(2, SEQ), \
148  BOOST_PP_SEQ_ELEM(3, SEQ), adamantine::SolidLiquidPowder, \
149  dealii::MemorySpace::Default>(communicator, database, timers);
150 
151 #define RUN_SOLID_LIQUID_POWDER_DEVICE(TUPLE) \
152  BOOST_PP_SEQ_FOR_EACH_PRODUCT( \
153  RUN_SOLID_LIQUID_POWDER_DEV_IF, \
154  ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER))
155 
156 std::tuple<int, int, int>
157 get_material_template_parameters(boost::property_tree::ptree &database)
158 {
159  // We need to detect the degree of the polynomial. There are two cases. First,
160  // we are using a table format. In this case, we return zero. Second, we are
161  // using the polynomial format. In this case, we need to loop over all the
162  // materials, all the states, and all the properties to determine the
163  // polynomial order.
164 
165  unsigned int p_order = 0;
166  unsigned int n_material_states = 0;
167  // PropertyTreeInput materials.property_format
168  bool use_table = database.get<std::string>("property_format") == "table";
169 
170  // PropertyTreeInput materials.n_materials
171  unsigned int const n_materials = database.get<unsigned int>("n_materials");
172  // Find all the material_ids being used.
173  std::vector<dealii::types::material_id> material_ids;
174  for (dealii::types::material_id id = 0;
175  id < dealii::numbers::invalid_material_id; ++id)
176  {
177  if (database.count("material_" + std::to_string(id)) != 0)
178  material_ids.push_back(id);
179  if (material_ids.size() == n_materials)
180  break;
181  }
182 
183  for (auto const material_id : material_ids)
184  {
185  // Get the material property tree.
186  boost::property_tree::ptree const &material_database =
187  database.get_child("material_" + std::to_string(material_id));
188  // For each material, loop over the possible states.
189  for (unsigned int state = 0;
191  {
192  // The state may or may not exist for the material.
193  boost::optional<boost::property_tree::ptree const &> state_database =
194  material_database.get_child_optional(
196  if (state_database)
197  {
198  // For each state, loop over the possible properties.
199  for (unsigned int p = 0; p < adamantine::g_n_state_properties; ++p)
200  {
201  // The property may or may not exist for that state
202  boost::optional<std::string> const property =
203  state_database.get().get_optional<std::string>(
205  // If the property exists, put it in the map. If the property does
206  // not exist, we have a nullptr.
207  if (property)
208  {
209  n_material_states = std::max(state + 1, n_material_states);
210  if (!use_table)
211  {
212  // Remove blank spaces
213  std::string property_string = property.get();
214  property_string.erase(std::remove_if(property_string.begin(),
215  property_string.end(),
216  [](unsigned char x)
217  { return std::isspace(x); }),
218  property_string.end());
219  std::vector<std::string> parsed_property;
220  boost::split(parsed_property, property_string,
221  [](char c) { return c == ','; });
222  p_order = std::max(
223  static_cast<unsigned int>(parsed_property.size() - 1),
224  p_order);
225  }
226  }
227  }
228  }
229  }
230  }
231 
232  // Sanity check
234  p_order < 5,
235  "Error when computing the polynomial order of the material properties");
237  n_material_states > 0 && n_material_states < 4,
238  "Error when computing the number of material states");
239 
240  return std::make_tuple(n_materials, p_order, n_material_states);
241 }
242 
243 int main(int argc, char *argv[])
244 {
245 #ifdef ADAMANTINE_WITH_CALIPER
246  CALI_MARK_BEGIN("main");
247 #endif
248 
249  dealii::Utilities::MPI::MPI_InitFinalize mpi_initialization(
250  argc, argv, dealii::numbers::invalid_unsigned_int);
251  MPI_Comm communicator = MPI_COMM_WORLD;
252 
253 #ifdef ADAMANTINE_WITH_ADIAK
254  adiak_init(&communicator);
255  adiak::user();
256  adiak::launchdate();
257  adiak::executablepath();
258  adiak::libraries();
259  adiak::cmdline();
260  adiak::clustername();
261  adiak::jobsize();
262 #endif
263 
264  std::vector<adamantine::Timer> timers;
265  initialize_timers(communicator, timers);
266  timers[adamantine::main].start();
267  bool profiling = false;
268  try
269  {
270  namespace boost_po = boost::program_options;
271 
272  // Get the name of the input file from the command line.
273  // First declare the possible options.
274  boost_po::options_description description("Options:");
275  description.add_options()("help,h", "Produce help message.")(
276  "input-file,i", boost_po::value<std::string>(),
277  "Name of the input file.")(
278  "output-dir,o", boost_po::value<std::string>(),
279  "Output directory; defaults to the current working directory.");
280 
281  // Declare a map that will contains the values read. Parse the command
282  // line and finally populate the map.
283  boost_po::variables_map map;
284  auto parsed_line = boost_po::command_line_parser(argc, argv)
285  .options(description)
286  .allow_unregistered()
287  .run();
288  boost_po::store(parsed_line, map);
289  boost_po::notify(map);
290  // Output the help message if asked for
291  if (map.count("help") == 1)
292  {
293  std::cout << description << std::endl;
294  return 0;
295  }
296 
297  // Exit gracefully when --kokkos-help is passed
298  std::vector<std::string> unrecognized_options =
299  boost_po::collect_unrecognized(parsed_line.options,
300  boost_po::include_positional);
301  for (auto const &options : unrecognized_options)
302  {
303  if (options == "--kokkos-help")
304  {
305  return 0;
306  }
307  }
308 
309  // Read the input.
310  std::string const filename = map["input-file"].as<std::string>();
311  adamantine::wait_for_file(filename, "Waiting for input file: " + filename);
312  boost::property_tree::ptree database;
313  if (std::filesystem::path(filename).extension().native() == ".json")
314  {
315  boost::property_tree::json_parser::read_json(filename, database);
316  }
317  else
318  {
319  boost::property_tree::info_parser::read_info(filename, database);
320  }
321  try
322  {
324  }
325  catch (std::runtime_error const &exception)
326  {
327  std::cerr << exception.what() << std::endl;
328  return 0;
329  }
330 
331  // Abusing the database to pull out the output dir when writing the output
332  if (map.count("output-dir") == 1)
333  {
334  try
335  {
336  std::string outdir =
337  std::filesystem::absolute(map["output-dir"].as<std::string>())
338  .string() +
339  std::filesystem::path::preferred_separator;
340  std::filesystem::create_directories(outdir);
341 
342  database.put("post_processor.output_dir", outdir);
343  }
344  catch (std::runtime_error const &exception)
345  {
346  std::cerr << exception.what() << std::endl;
347  return 0;
348  }
349  }
350 
351  // Make adamantine behave a bit better and not lock up if not in cwd of
352  // current input file
353  std::filesystem::current_path(
354  std::filesystem::absolute(filename).parent_path());
355 
356 #ifdef ADAMANTINE_WITH_CALIPER
357  cali::ConfigManager caliper_manager;
358 #endif
359  boost::optional<boost::property_tree::ptree &> profiling_optional_database =
360  database.get_child_optional("profiling");
361  if (profiling_optional_database)
362  {
363  auto profiling_database = profiling_optional_database.get();
364  // PropertyTreeInput profiling.timer
365  if (profiling_database.get("timer", false))
366  profiling = true;
367 #ifdef ADAMANTINE_WITH_CALIPER
368  // PropertyTreeInput profiling.caliper
369  auto caliper_optional_string =
370  profiling_database.get_optional<std::string>("caliper");
371  if (caliper_optional_string)
372  caliper_manager.add(caliper_optional_string.get().c_str());
373 #endif
374  }
375 #ifdef ADAMANTINE_WITH_CALIPER
376  caliper_manager.start();
377 #endif
378 
379  boost::optional<boost::property_tree::ptree &> ensemble_optional_database =
380  database.get_child_optional("ensemble");
381  bool ensemble_calc = false;
382  if (ensemble_optional_database)
383  {
384  auto ensemble_database = ensemble_optional_database.get();
385  // PropertyTreeInput ensemble.ensemble_simulation
386  ensemble_calc = ensemble_database.get<bool>("ensemble_simulation", false);
387  }
388 
389  boost::property_tree::ptree geometry_database =
390  database.get_child("geometry");
391  // PropertyTreeInput geometry.dim
392  int const dim = geometry_database.get<int>("dim");
393 
394  // Get the polynomial order used in the material properties
395  auto const [n_materials, p_order, n_material_states] =
396  get_material_template_parameters(database.get_child("materials"));
397  adamantine::ASSERT_THROW(p_order < 5,
398  "Material properties have too many coefficients.");
399 
400  unsigned int rank = dealii::Utilities::MPI::this_mpi_process(communicator);
401 
402  // PropertyTreeInput memory_space
403  std::string memory_space =
404  database.get<std::string>("memory_space", "host");
405 
406 #ifdef ADAMANTINE_WITH_ADIAK
407  if (memory_space == "device")
408  adiak::value("MemorySpace", "Device");
409  else
410  adiak::value("MemorySpace", "Host");
411 #endif
412 
413  std::tuple<int, int, int> template_parameters(dim, n_materials, p_order);
414 
415  if (ensemble_calc)
416  {
417  if (memory_space == "device")
418  {
419  // TODO: Add device version of run_ensemble and call it here
421  false,
422  "Device version of ensemble simulations not yet implemented.");
423  }
424 
425  if (rank == 0)
426  std::cout << "Starting ensemble simulation" << std::endl;
427 
428  if (n_material_states == 1)
429  {
430  RUN_ENSEMBLE_SOLID_HOST(template_parameters);
431  }
432  else if (n_material_states == 2)
433  {
434  RUN_ENSEMBLE_SOLID_LIQUID_HOST(template_parameters);
435  }
436  else
437  {
438  RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST(template_parameters);
439  }
440  }
441  else
442  {
443  if (rank == 0)
444  std::cout << "Starting non-ensemble simulation" << std::endl;
445 
446  if (memory_space == "device")
447  {
448  if (n_material_states == 1)
449  {
450  RUN_SOLID_DEVICE(template_parameters);
451  }
452  else if (n_material_states == 2)
453  {
454  RUN_SOLID_LIQUID_DEVICE(template_parameters);
455  }
456  else
457  {
458  RUN_SOLID_LIQUID_POWDER_DEVICE(template_parameters);
459  }
460  }
461  else
462  {
463  if (n_material_states == 1)
464  {
465  RUN_SOLID_DEVICE(template_parameters);
466  }
467  else if (n_material_states == 2)
468  {
469  RUN_SOLID_LIQUID_DEVICE(template_parameters);
470  }
471  else
472  {
473  RUN_SOLID_LIQUID_POWDER_DEVICE(template_parameters);
474  }
475  }
476  }
477 
478  if (rank == 0)
479  std::cout << "Simulation done" << std::endl;
480 
481 #ifdef ADAMANTINE_WITH_CALIPER
482  CALI_MARK_END("main");
483  caliper_manager.flush();
484 #endif
485  }
486  catch (boost::bad_any_cast &exception)
487  {
488  std::cerr << std::endl;
489  std::cerr << "Aborting." << std::endl;
490  std::cerr << "Error: " << exception.what() << std::endl << std::endl;
491  std::cerr << "There is a problem with the input file." << std::endl;
492  std::cerr << "Make sure that the input file is correct" << std::endl;
493  std::cerr << "and that you are using the following command" << std::endl;
494  std::cerr << "to run adamantine:" << std::endl;
495  std::cerr << "./adamantine --input-file=my_input_file" << std::endl;
496  std::cerr << std::endl;
497  }
498  catch (std::exception &exception)
499  {
500  std::cerr << std::endl;
501  std::cerr << "Aborting." << std::endl;
502  std::cerr << "Error: " << exception.what() << std::endl;
503  std::cerr << std::endl;
504 
505  return 1;
506  }
507  catch (...)
508  {
509  std::cerr << std::endl;
510  std::cerr << "Aborting." << std::endl;
511  std::cerr << "No error message." << std::endl;
512  std::cerr << std::endl;
513 
514  return 1;
515  }
516 
517  timers[adamantine::main].stop();
518  if (profiling == true)
519  for (auto &timer : timers)
520  timer.print();
521 
522 #ifdef ADAMANTINE_WITH_ADIAK
523  adiak::fini();
524 #endif
525 
526  return 0;
527 }
int main(int argc, char *argv[])
Definition: adamantine.cc:243
#define RUN_ENSEMBLE_SOLID_HOST(TUPLE)
Definition: adamantine.cc:39
#define RUN_SOLID_LIQUID_DEVICE(TUPLE)
Definition: adamantine.cc:137
std::tuple< int, int, int > get_material_template_parameters(boost::property_tree::ptree &database)
Definition: adamantine.cc:157
#define RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST(TUPLE)
Definition: adamantine.cc:67
#define RUN_SOLID_DEVICE(TUPLE)
Definition: adamantine.cc:123
#define RUN_SOLID_LIQUID_POWDER_DEVICE(TUPLE)
Definition: adamantine.cc:151
#define RUN_ENSEMBLE_SOLID_LIQUID_HOST(TUPLE)
Definition: adamantine.cc:53
void initialize_timers(MPI_Comm const &communicator, std::vector< adamantine::Timer > &timers)
Definition: adamantine.hh:189
void validate_input_database(boost::property_tree::ptree &database)
static std::array< std::string, 3 > const material_state_names
Definition: types.hh:107
void wait_for_file(std::string const &filename, std::string const &message)
Definition: utils.hh:24
void ASSERT_THROW(bool cond, std::string const &message)
Definition: utils.hh:70
static std::array< std::string, 15 > const state_property_names
Definition: types.hh:122
static unsigned constexpr int g_n_state_properties
Definition: types.hh:69
static unsigned constexpr int n_material_states