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