adamantine
validate_input_database.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 <Boundary.hh>
6 #include <types.hh>
7 #include <utils.hh>
9 
10 #include <boost/algorithm/string/predicate.hpp>
11 
12 #include <algorithm>
13 
14 namespace adamantine
15 {
16 void validate_input_database(boost::property_tree::ptree &database)
17 {
18  // Tree: physics
19  // PropertyTreeInput physics.thermal
20  bool const use_thermal_physics = database.get<bool>("physics.thermal");
21  // PropertyTreeInput physics.mechanical
22  bool const use_mechanical_physics = database.get<bool>("physics.mechanical");
23  ASSERT_THROW(use_thermal_physics || use_mechanical_physics,
24  "Both thermal and mechanical physics are disabled");
25 
26  // Tree: boundary
27  // Read the boundary condition type and check for disallowed combinations.
28  // Store if we use radiative or convective boundary so we can check later that
29  // the appropriate material properties are saved.
30  BoundaryType boundary_type = BoundaryType::invalid;
31  // Parse the string
32  std::string delimiter = ",";
33  auto parse_boundary_type = [&](std::string const &boundary,
34  BoundaryType &line_boundary_type,
35  bool &thermal_bc, bool &mechanical_bc)
36  {
37  if (boundary == "adiabatic")
38  {
39  ASSERT_THROW(!(line_boundary_type & BoundaryType::radiative) ||
40  !(line_boundary_type & BoundaryType::convective),
41  "adiabatic condition cannot be combined with another type "
42  "of thermal boundary condition.");
43  line_boundary_type |= BoundaryType::adiabatic;
44  thermal_bc = true;
45  }
46  else
47  {
48  if (boundary == "radiative")
49  {
50  ASSERT_THROW(!(line_boundary_type & BoundaryType::adiabatic),
51  "adiabatic condition cannot be combined with another type "
52  "of thermal boundary condition.");
53  line_boundary_type |= BoundaryType::radiative;
54  boundary_type |= BoundaryType::radiative;
55  thermal_bc = true;
56  }
57  else if (boundary == "convective")
58  {
59  ASSERT_THROW(!(line_boundary_type & BoundaryType::adiabatic),
60  "adiabatic condition cannot be combined with another type "
61  "of thermal boundary condition.");
62  line_boundary_type |= BoundaryType::convective;
63  boundary_type |= BoundaryType::convective;
64  thermal_bc = true;
65  }
66  else if (boundary == "clamped")
67  {
69  !(line_boundary_type & BoundaryType::traction_free),
70  "Mechanical boundary conditions cannot be combined together.");
71  line_boundary_type |= BoundaryType::clamped;
72  mechanical_bc = true;
73  }
74  else if (boundary == "traction_free")
75  {
77  !(line_boundary_type & BoundaryType::clamped),
78  "Mechanical boundary conditions cannot be combined together.");
79  line_boundary_type |= BoundaryType::traction_free;
80  mechanical_bc = true;
81  }
82  else
83  {
84  ASSERT_THROW(false, "Unknown boundary type.");
85  }
86  }
87  };
88  auto const boundary_database = database.get_child("boundary");
89  for (auto const &child_pair : boundary_database)
90  {
91  size_t pos_str = 0;
92  bool thermal_bc = false;
93  bool mechanical_bc = false;
94  BoundaryType line_boundary_type = BoundaryType::invalid;
95  std::string boundary_type_str = "invalid";
96  if (child_pair.first == "type")
97  {
98  boundary_type_str = child_pair.second.data();
99  }
100  else
101  {
102  boundary_type_str =
103  boundary_database.get<std::string>(child_pair.first + ".type");
104  }
105 
106  while ((pos_str = boundary_type_str.find(delimiter)) != std::string::npos)
107  {
108  std::string boundary = boundary_type_str.substr(0, pos_str);
109  parse_boundary_type(boundary, line_boundary_type, thermal_bc,
110  mechanical_bc);
111  boundary_type_str.erase(0, pos_str + delimiter.length());
112  }
113  parse_boundary_type(boundary_type_str, line_boundary_type, thermal_bc,
114  mechanical_bc);
115 
116  if (use_thermal_physics)
117  {
118  ASSERT_THROW(thermal_bc, "Missing thermal boundary condition.");
119  }
120  if (use_mechanical_physics)
121  {
122  ASSERT_THROW(mechanical_bc, "Missing mechanical boundary condition.");
123  }
124  }
125 
126  // Tree: discretization.thermal
127  if (use_thermal_physics)
128  {
129  // PropertyTreeInput discretization.thermal.fe_degree
130  unsigned int const fe_degree =
131  database.get<unsigned int>("discretization.thermal.fe_degree");
132  ASSERT_THROW(fe_degree > 0 && fe_degree < 6,
133  "fe_degree should be between 1 and 5.");
134 
135  // PropertyTreeInput discretization.thermal.quadrature
136  boost::optional<std::string> quadrature_type_optional =
137  database.get_optional<std::string>("discretization.thermal.quadrature");
138 
139  if (quadrature_type_optional)
140  {
141  std::string quadrature_type = quadrature_type_optional.get();
142  if (!((boost::iequals(quadrature_type, "gauss") ||
143  (boost::iequals(quadrature_type, "lobatto")))))
144  {
145  ASSERT_THROW(false, "Unknown quadrature type.");
146  }
147  }
148  }
149 
150  // Tree: geometry
151  unsigned int dim = database.get<unsigned int>("geometry.dim");
152  ASSERT_THROW((dim == 2) || (dim == 3), "dim should be 2 or 3");
153 
154  bool use_powder = database.get("geometry.use_powder", false);
155 
156  if (use_powder)
157  {
158  double powder_layer = database.get<double>("geometry.powder_layer");
159  ASSERT_THROW(powder_layer >= 0.0, "powder_layer must be non-negative.");
160  }
161 
162  bool material_deposition =
163  database.get("geometry.material_deposition", false);
164 
165  if (material_deposition)
166  {
167  std::string method =
168  database.get<std::string>("geometry.material_deposition_method");
169 
170  ASSERT_THROW(
171  (method == "file" || method == "scan_paths"),
172  "Method type for material deposition, '" + method +
173  "', is not recognized. Valid options are: 'file' and 'scan_paths'");
174 
175  if (method == "file")
176  {
177  ASSERT_THROW(database.count("geometry.material_deposition_file") != 0,
178  "If the material deposition method is 'file', "
179  "'material_deposition_file' must be given.");
180  }
181  else
182  {
183  ASSERT_THROW(database.get_child("geometry").count("deposition_length") !=
184  0,
185  "If the material deposition method is 'scan_path', "
186  "'deposition_length' must be given.");
187  ASSERT_THROW(database.get_child("geometry").count("deposition_height") !=
188  0,
189  "If the material deposition method is 'scan_path', "
190  "'deposition_height' must be given.");
191  if (dim == 3)
192  {
193  ASSERT_THROW(database.get_child("geometry").count("deposition_width") !=
194  0,
195  "If the material deposition method is 'scan_path', "
196  "'deposition_width' must be given.");
197  }
198  ASSERT_THROW(
199  database.get_child("geometry").count("deposition_lead_time") != 0,
200  "If the material deposition method is 'scan_path', "
201  "'deposition_lead_time' must be given.");
202  }
203  }
204 
205  bool import_mesh = database.get<bool>("geometry.import_mesh");
206  if (import_mesh)
207  {
208  ASSERT_THROW(database.get_child("geometry").count("mesh_file") != 0,
209  "If the the mesh is imported, 'mesh_file' must be given.");
210  ASSERT_THROW(database.get_child("geometry").count("mesh_format") != 0,
211  "If the the mesh is imported, 'mesh_format' must be given.");
212  }
213  else
214  {
215  ASSERT_THROW(database.get_child("geometry").count("length") != 0,
216  "If the the mesh is not imported, 'length' must be given.");
217  ASSERT_THROW(database.get_child("geometry").count("height") != 0,
218  "If the the mesh is not imported, 'height' must be given.");
219  if (dim == 3)
220  {
221  ASSERT_THROW(database.get_child("geometry").count("width") != 0,
222  "If the the mesh is not imported, 'width' must be given.");
223  }
224  }
225 
226  // Tree: materials
227  unsigned int n_materials =
228  database.get<unsigned int>("materials.n_materials");
229 
230  std::string property_format =
231  database.get<std::string>("materials.property_format");
232  ASSERT_THROW((property_format == "table") ||
233  (property_format == "polynomial"),
234  "property_format should be table or polynomial.");
235 
236  for (dealii::types::material_id id = 0; id < n_materials; ++id)
237  {
238  ASSERT_THROW(database.get_child("materials")
239  .count("material_" + std::to_string(id)) != 0,
240  "Number of material subtrees does not match the set number of "
241  "materials.");
242 
243  bool has_a_valid_state = false;
244  for (unsigned int state_index = 0;
245  state_index < material_state_names.size(); ++state_index)
246  {
247  if (database.get_child("materials")
248  .get_child("material_" + std::to_string(id))
249  .count(material_state_names.at(state_index)) != 0)
250  {
251  has_a_valid_state = true;
252 
253  ASSERT_THROW(database.get_child("materials")
254  .get_child("material_" + std::to_string(id))
255  .get_child(material_state_names.at(state_index))
256  .count("density") != 0,
257  "Each state needs a user-specified density.");
258  ASSERT_THROW(database.get_child("materials")
259  .get_child("material_" + std::to_string(id))
260  .get_child(material_state_names.at(state_index))
261  .count("specific_heat") != 0,
262  "Each state needs a user-specified specific heat.");
263  ASSERT_THROW(database.get_child("materials")
264  .get_child("material_" + std::to_string(id))
265  .get_child(material_state_names.at(state_index))
266  .count("thermal_conductivity_x") != 0,
267  "Each state needs a user-specified specific thermal "
268  "conductivity x.");
269  ASSERT_THROW(database.get_child("materials")
270  .get_child("material_" + std::to_string(id))
271  .get_child(material_state_names.at(state_index))
272  .count("thermal_conductivity_z") != 0,
273  "Each state needs a user-specified specific thermal "
274  "conductivity z.");
275 
276  if (dim == 3)
277  {
278  ASSERT_THROW(database.get_child("materials")
279  .get_child("material_" + std::to_string(id))
280  .get_child(material_state_names.at(state_index))
281  .count("thermal_conductivity_y") != 0,
282  "Each state needs a user-specified specific thermal "
283  "conductivity y.");
284  }
285 
286  if (boundary_type & BoundaryType::convective)
287  {
288  ASSERT_THROW(database.get_child("materials")
289  .get_child("material_" + std::to_string(id))
290  .get_child(material_state_names.at(state_index))
291  .count("convection_heat_transfer_coef") != 0,
292  "Convective BCs require a user-specified convection "
293  "heat transfer coefficient.");
294  }
295 
296  if (boundary_type & BoundaryType::radiative)
297  {
298  ASSERT_THROW(database.get_child("materials")
299  .get_child("material_" + std::to_string(id))
300  .get_child(material_state_names.at(state_index))
301  .count("emissivity") != 0,
302  "Radiative BCs require a user-specified emissivity.");
303  }
304 
305  // For now I'm leaving the error checking for the polynomials and tables
306  // for individual properties to the MaterialProperty class. I don't
307  // think it makes sense to duplicate that logic here.
308  }
309  }
310 
311  ASSERT_THROW(
312  has_a_valid_state == true,
313  "Material without any valid state (solid, powder, or liquid).");
314 
315  if (boundary_type & BoundaryType::convective)
316  {
317  ASSERT_THROW(
318  database.get_child("materials")
319  .get_child("material_" + std::to_string(id))
320  .count("convection_temperature_infty") != 0,
321  "Convective BCs require setting 'convection_temperature_infty'.");
322  }
323 
324  if (boundary_type & BoundaryType::radiative)
325  {
326  ASSERT_THROW(
327  database.get_child("materials")
328  .get_child("material_" + std::to_string(id))
329  .count("radiation_temperature_infty") != 0,
330  "Radiative BCs require setting 'radiation_temperature_infty'.");
331  }
332  }
333 
334  // Tree: memory_space
335  boost::optional<std::string> memory_space_optional =
336  database.get_optional<std::string>("memory_space");
337  if (memory_space_optional)
338  {
339  std::string memory_space = memory_space_optional.get();
340  ASSERT_THROW(
341  (memory_space == "device" || memory_space == "host"),
342  "Method type for memory space, '" + memory_space +
343  "', is not recognized. Valid options are: 'host' and 'device'");
344  }
345 
346  // Tree: post_processor
347  ASSERT_THROW(database.get_child("post_processor").count("filename_prefix") !=
348  0,
349  "The filename prefix for the postprocessor must be specified.");
350 
351  // Tree: refinement
352  ASSERT_THROW(database.count("refinement") != 0,
353  "A refinement section of the input file must exist.");
354 
355  // Tree: sources
356  unsigned int n_beams = database.get<unsigned int>("sources.n_beams");
357  for (unsigned int beam_index = 0; beam_index < n_beams; ++beam_index)
358  {
359  std::string beam_type = database.get<std::string>(
360  "sources.beam_" + std::to_string(beam_index) + ".type");
361  ASSERT_THROW(boost::iequals(beam_type, "goldak") ||
362  boost::iequals(beam_type, "electron_beam") ||
363  boost::iequals(beam_type, "cube"),
364  "Beam type, '" + beam_type +
365  "', is not recognized. Valid options are: 'goldak', "
366  "'electron_beam', and 'cube'.");
367  ASSERT_THROW(database.get_child("sources")
368  .get_child("beam_" + std::to_string(beam_index))
369  .count("scan_path_file") != 0,
370  "A scan path file for each beam must be given.");
371 
372  std::string file_format =
373  database.get<std::string>("sources.beam_" + std::to_string(beam_index) +
374  ".scan_path_file_format");
375  ASSERT_THROW(boost::iequals(file_format, "segment") ||
376  boost::iequals(file_format, "event_series"),
377  "Scan path file format, '" + file_format +
378  "', is not recognized. Valid options are: 'segment' and "
379  "'event_series'.");
380  ASSERT_THROW(database.get<double>("sources.beam_" +
381  std::to_string(beam_index) + ".depth") >=
382  0.0,
383  "Heat source depth must be non-negative.");
384 
385  double absorption_efficiency =
386  database.get<double>("sources.beam_" + std::to_string(beam_index) +
387  ".absorption_efficiency");
388  ASSERT_THROW(absorption_efficiency >= 0.0 && absorption_efficiency <= 1.0,
389  "Heat source absorption efficiency must be between 0 and 1.");
390  }
391 
392  // Tree: time_stepping
393  std::string time_stepping_method =
394  database.get<std::string>("time_stepping.method");
395  ASSERT_THROW(boost::iequals(time_stepping_method, "forward_euler") ||
396  boost::iequals(time_stepping_method, "rk_third_order") ||
397  boost::iequals(time_stepping_method, "rk_fourth_order"),
398  "Time stepping method, '" + time_stepping_method +
399  "', is not recognized. Valid options are: 'forward_euler', "
400  "'rk_third_order', and 'rk_fourth_order'.");
401 
402  if (database.get("time.scan_path_for_duration", false))
403  {
404  ASSERT_THROW(database.get<double>("time_stepping.duration") >= 0.0,
405  "Time stepping duration must be non-negative.");
406  }
407 
408  ASSERT_THROW(database.get<double>("time_stepping.time_step") >= 0.0,
409  "Time step must be non-negative.");
410 
411  // Tree: experiment
412  // I'm not checking for the existence of the experimental files here, that's
413  // still done in `adamantine::read_experimental_data_point_cloud` and
414  // `adamantine::RayTracing`.
415  boost::optional<boost::property_tree::ptree &> experiment_optional_database =
416  database.get_child_optional("experiment");
417  if (experiment_optional_database)
418  {
419  bool experiment_active =
420  database.get("experiment.read_in_experimental_data", false);
421  if (experiment_active)
422  {
423  ASSERT_THROW(database.get_child("experiment").count("file") != 0,
424  "If reading experimental data, a file must be given.");
425 
426  ASSERT_THROW(database.get_child("experiment").count("last_frame") != 0,
427  "If reading experimental data, a last frame index "
428  "must be given.");
429 
430  std::string experiment_format =
431  database.get<std::string>("experiment.format");
432  ASSERT_THROW(boost::iequals(experiment_format, "point_cloud") ||
433  boost::iequals(experiment_format, "ray"),
434  "Experiment format must be 'point_cloud' or 'ray'.");
435 
436  unsigned int first_frame_index =
437  database.get<unsigned int>("experiment.first_frame", 0);
438  unsigned int last_frame_index =
439  database.get<unsigned int>("experiment.last_frame");
440  ASSERT_THROW(last_frame_index >= first_frame_index,
441  "When reading experimental data, the last frame index "
442  "cannot be lower than the first frame index.");
443 
444  unsigned int first_camera_id =
445  database.get<unsigned int>("experiment.first_camera_id");
446  unsigned int last_camera_id =
447  database.get<unsigned int>("experiment.last_camera_id");
448  ASSERT_THROW(last_camera_id >= first_camera_id,
449  "When reading experimental data, the last camera id cannot "
450  "be lower than the first camera id.");
451 
452  ASSERT_THROW(
453  database.get_child("experiment").count("log_filename") != 0,
454  "If reading experimental data, a log filename must be given.");
455  }
456  }
457 
458  // Tree: ensemble
459  // We check the input in ensemble_management.cc
460 
461  // Tree: data_assimilation
462  boost::optional<double> convergence_tolerance =
463  database.get_optional<double>("data_assimilation.convergence_tolerance");
464  if (convergence_tolerance)
465  {
466  ASSERT_THROW(
467  convergence_tolerance.get() >= 0.0,
468  "The data assimilation convergene tolerance must be non-negative.");
469  }
470 
471  std::string localization_cutoff_function_str =
472  database.get("data_assimilation.localization_cutoff_function", "none");
473 
474  if (!(boost::iequals(localization_cutoff_function_str, "gaspari_cohn") ||
475  boost::iequals(localization_cutoff_function_str, "step_function") ||
476  boost::iequals(localization_cutoff_function_str, "none")))
477  {
478  ASSERT_THROW(false, "Unknown localization cutoff function. Valid options "
479  "are 'gaspari_cohn', 'step_function', and 'none'.");
480  }
481 
482  // Tree: units
483  boost::optional<std::string> mesh_unit =
484  database.get_optional<std::string>("units.mesh");
485  if (mesh_unit && (!(boost::iequals(mesh_unit.get(), "millimeter") ||
486  boost::iequals(mesh_unit.get(), "centimeter") ||
487  boost::iequals(mesh_unit.get(), "inch") ||
488  boost::iequals(mesh_unit.get(), "meter"))))
489  {
490  ASSERT_THROW(false, "Unknown unit associated with the mesh. Valid options "
491  "are `millimeter`, `centimeter`, `inch`, and `meter`");
492  }
493 
494  boost::optional<std::string> heat_source_power_unit =
495  database.get_optional<std::string>("units.heat_source.power");
496  if (heat_source_power_unit &&
497  (!(boost::iequals(heat_source_power_unit.get(), "milliwatt") ||
498  boost::iequals(heat_source_power_unit.get(), "watt"))))
499  {
500  ASSERT_THROW(false, "Unknown unit associated with the power of the heat "
501  "source. Valid options are `milliwatt`, and `watt`");
502  }
503 
504  boost::optional<std::string> heat_source_velocity_unit =
505  database.get_optional<std::string>("units.heat_source.velocity");
506  if (heat_source_velocity_unit &&
507  (!(boost::iequals(heat_source_velocity_unit.get(), "millimeter/second") ||
508  boost::iequals(heat_source_velocity_unit.get(), "centimeter/second") ||
509  boost::iequals(heat_source_velocity_unit.get(), "meter/second"))))
510  {
511  ASSERT_THROW(false, "Unknown unit associated with the velocity of the heat "
512  "source. Valid options are `millimeter/second`, "
513  "`centimeter/second`, and `meter/second`");
514  }
515 
516  boost::optional<std::string> heat_source_dimension_unit =
517  database.get_optional<std::string>("units.heat_source.dimension");
518  if (heat_source_dimension_unit &&
519  (!(boost::iequals(heat_source_dimension_unit.get(), "millimeter") ||
520  boost::iequals(heat_source_dimension_unit.get(), "centimeter") ||
521  boost::iequals(heat_source_dimension_unit.get(), "inch") ||
522  boost::iequals(heat_source_dimension_unit.get(), "meter"))))
523  {
524  ASSERT_THROW(
525  false,
526  "Unknown unit associated with the dimension of the heat source. Valid "
527  "options are `millimeter`, `centimeter`, `inch`, and `meter`");
528  }
529 
530  boost::optional<std::string> heat_source_scan_path_unit =
531  database.get_optional<std::string>("units.heat_source.scan_path");
532  if (heat_source_scan_path_unit &&
533  (!(boost::iequals(heat_source_scan_path_unit.get(), "millimeter") ||
534  boost::iequals(heat_source_scan_path_unit.get(), "centimeter") ||
535  boost::iequals(heat_source_scan_path_unit.get(), "inch") ||
536  boost::iequals(heat_source_scan_path_unit.get(), "meter"))))
537  {
538  ASSERT_THROW(false,
539  "Unknown unit associated with the scan path. Valid options "
540  "are `millimeter`, `centimeter`, `inch`, and `meter`");
541  }
542 }
543 } // namespace adamantine
void validate_input_database(boost::property_tree::ptree &database)
static std::array< std::string, 3 > const material_state_names
Definition: types.hh:107
@ convective
Definition: Boundary.hh:26
@ traction_free
Definition: Boundary.hh:28
void ASSERT_THROW(bool cond, std::string const &message)
Definition: utils.hh:70