/* SPDX-FileCopyrightText: Copyright (c) 2016 - 2025, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include "adamantine.hh" #include #include #include #include #include #include #include #include #include #include #ifdef ADAMANTINE_WITH_ADIAK #include #endif #ifdef ADAMANTINE_WITH_CALIPER #include #endif // Define macros that replace nested ifs #define RUN_ENSEMBLE_SOLID_HOST_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run_ensemble(communicator, database, timers); #define RUN_ENSEMBLE_SOLID_HOST(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_ENSEMBLE_SOLID_HOST_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_ENSEMBLE_SOLID_LIQUID_HOST_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run_ensemble(communicator, database, timers); #define RUN_ENSEMBLE_SOLID_LIQUID_HOST(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_ENSEMBLE_SOLID_LIQUID_HOST_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run_ensemble(communicator, database, timers); #define RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_SOLID_HOST_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run(communicator, database, timers); #define RUN_SOLID_HOST(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_SOLID_HOST_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_SOLID_LIQUID_HOST_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run(communicator, database, timers); #define RUN_SOLID_LIQUID_HOST(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_SOLID_LIQUID_HOST_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_SOLID_LIQUID_POWDER_HOST_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run(communicator, database, timers); #define RUN_SOLID_LIQUID_POWDER_HOST(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_SOLID_LIQUID_POWDER_HOST_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_SOLID_DEV_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run(communicator, database, timers); #define RUN_SOLID_DEVICE(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_SOLID_DEV_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_SOLID_LIQUID_DEV_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run(communicator, database, timers); #define RUN_SOLID_LIQUID_DEVICE(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_SOLID_LIQUID_DEV_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) #define RUN_SOLID_LIQUID_POWDER_DEV_IF(z, SEQ) \ if (BOOST_PP_SEQ_ELEM(0, SEQ) == \ std::tuple(BOOST_PP_SEQ_ELEM(1, SEQ), \ BOOST_PP_SEQ_ELEM(2, SEQ), \ BOOST_PP_SEQ_ELEM(3, SEQ))) \ run(communicator, database, timers); #define RUN_SOLID_LIQUID_POWDER_DEVICE(TUPLE) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT( \ RUN_SOLID_LIQUID_POWDER_DEV_IF, \ ((TUPLE))(ADAMANTINE_DIM)(ADAMANTINE_N_MATERIALS)(ADAMANTINE_P_ORDER)) std::tuple get_material_template_parameters(boost::property_tree::ptree &database) { // We need to detect the degree of the polynomial. There are two cases. First, // we are using a table format. In this case, we return zero. Second, we are // using the polynomial format. In this case, we need to loop over all the // materials, all the states, and all the properties to determine the // polynomial order. unsigned int p_order = 0; unsigned int n_material_states = 0; // PropertyTreeInput materials.property_format bool use_table = database.get("property_format") == "table"; // PropertyTreeInput materials.n_materials unsigned int const n_materials = database.get("n_materials"); // Find all the material_ids being used. std::vector material_ids; for (dealii::types::material_id id = 0; id < dealii::numbers::invalid_material_id; ++id) { if (database.count("material_" + std::to_string(id)) != 0) material_ids.push_back(id); if (material_ids.size() == n_materials) break; } for (auto const material_id : material_ids) { // Get the material property tree. boost::property_tree::ptree const &material_database = database.get_child("material_" + std::to_string(material_id)); // For each material, loop over the possible states. for (unsigned int state = 0; state < adamantine::SolidLiquidPowder::n_material_states; ++state) { // The state may or may not exist for the material. boost::optional state_database = material_database.get_child_optional( adamantine::material_state_names[state]); if (state_database) { // For each state, loop over the possible properties. for (unsigned int p = 0; p < adamantine::g_n_state_properties; ++p) { // The property may or may not exist for that state boost::optional const property = state_database.get().get_optional( adamantine::state_property_names[p]); // If the property exists, put it in the map. If the property does // not exist, we have a nullptr. if (property) { n_material_states = std::max(state + 1, n_material_states); if (!use_table) { // Remove blank spaces std::string property_string = property.get(); property_string.erase(std::remove_if(property_string.begin(), property_string.end(), [](unsigned char x) { return std::isspace(x); }), property_string.end()); std::vector parsed_property; boost::split(parsed_property, property_string, [](char c) { return c == ','; }); p_order = std::max( static_cast(parsed_property.size() - 1), p_order); } } } } } } // Sanity check adamantine::ASSERT_THROW( p_order < 5, "Error when computing the polynomial order of the material properties"); adamantine::ASSERT_THROW( n_material_states > 0 && n_material_states < 4, "Error when computing the number of material states"); return std::make_tuple(n_materials, p_order, n_material_states); } int main(int argc, char *argv[]) { #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_BEGIN("main"); #endif dealii::Utilities::MPI::MPI_InitFinalize mpi_initialization( argc, argv, dealii::numbers::invalid_unsigned_int); MPI_Comm communicator = MPI_COMM_WORLD; #ifdef ADAMANTINE_WITH_ADIAK adiak_init(&communicator); adiak::user(); adiak::launchdate(); adiak::executablepath(); adiak::libraries(); adiak::cmdline(); adiak::clustername(); adiak::jobsize(); #endif std::vector timers; initialize_timers(communicator, timers); timers[adamantine::main].start(); bool profiling = false; try { namespace boost_po = boost::program_options; // Get the name of the input file from the command line. // First declare the possible options. boost_po::options_description description("Options:"); description.add_options()("help,h", "Produce help message.")( "input-file,i", boost_po::value(), "Name of the input file.")( "output-dir,o", boost_po::value(), "Output directory; defaults to the current working directory."); // Declare a map that will contains the values read. Parse the command // line and finally populate the map. boost_po::variables_map map; auto parsed_line = boost_po::command_line_parser(argc, argv) .options(description) .allow_unregistered() .run(); boost_po::store(parsed_line, map); boost_po::notify(map); // Output the help message if asked for if (map.count("help") == 1) { std::cout << description << std::endl; return 0; } // Exit gracefully when --kokkos-help is passed std::vector unrecognized_options = boost_po::collect_unrecognized(parsed_line.options, boost_po::include_positional); for (auto const &options : unrecognized_options) { if (options == "--kokkos-help") { return 0; } } // Read the input. std::string const filename = map["input-file"].as(); adamantine::wait_for_file(filename, "Waiting for input file: " + filename); boost::property_tree::ptree database; if (std::filesystem::path(filename).extension().native() == ".json") { boost::property_tree::json_parser::read_json(filename, database); } else { boost::property_tree::info_parser::read_info(filename, database); } try { adamantine::validate_input_database(database); } catch (std::runtime_error const &exception) { std::cerr << exception.what() << std::endl; return 0; } // Abusing the database to pull out the output dir when writing the output if (map.count("output-dir") == 1) { try { std::string outdir = std::filesystem::absolute(map["output-dir"].as()) .string() + std::filesystem::path::preferred_separator; std::filesystem::create_directories(outdir); database.put("post_processor.output_dir", outdir); } catch (std::runtime_error const &exception) { std::cerr << exception.what() << std::endl; return 0; } } // Make adamantine behave a bit better and not lock up if not in cwd of // current input file std::filesystem::current_path( std::filesystem::absolute(filename).parent_path()); #ifdef ADAMANTINE_WITH_CALIPER cali::ConfigManager caliper_manager; #endif boost::optional profiling_optional_database = database.get_child_optional("profiling"); if (profiling_optional_database) { auto profiling_database = profiling_optional_database.get(); // PropertyTreeInput profiling.timer if (profiling_database.get("timer", false)) profiling = true; #ifdef ADAMANTINE_WITH_CALIPER // PropertyTreeInput profiling.caliper auto caliper_optional_string = profiling_database.get_optional("caliper"); if (caliper_optional_string) caliper_manager.add(caliper_optional_string.get().c_str()); #endif } #ifdef ADAMANTINE_WITH_CALIPER caliper_manager.start(); #endif boost::optional ensemble_optional_database = database.get_child_optional("ensemble"); bool ensemble_calc = false; if (ensemble_optional_database) { auto ensemble_database = ensemble_optional_database.get(); // PropertyTreeInput ensemble.ensemble_simulation ensemble_calc = ensemble_database.get("ensemble_simulation", false); } boost::property_tree::ptree geometry_database = database.get_child("geometry"); // PropertyTreeInput geometry.dim int const dim = geometry_database.get("dim"); // Get the polynomial order used in the material properties auto const [n_materials, p_order, n_material_states] = get_material_template_parameters(database.get_child("materials")); adamantine::ASSERT_THROW(p_order < 5, "Material properties have too many coefficients."); unsigned int rank = dealii::Utilities::MPI::this_mpi_process(communicator); // PropertyTreeInput memory_space std::string memory_space = database.get("memory_space", "host"); #ifdef ADAMANTINE_WITH_ADIAK if (memory_space == "device") adiak::value("MemorySpace", "Device"); else adiak::value("MemorySpace", "Host"); #endif std::tuple template_parameters(dim, n_materials, p_order); if (ensemble_calc) { if (memory_space == "device") { // TODO: Add device version of run_ensemble and call it here adamantine::ASSERT_THROW( false, "Device version of ensemble simulations not yet implemented."); } if (rank == 0) std::cout << "Starting ensemble simulation" << std::endl; if (n_material_states == 1) { RUN_ENSEMBLE_SOLID_HOST(template_parameters); } else if (n_material_states == 2) { RUN_ENSEMBLE_SOLID_LIQUID_HOST(template_parameters); } else { RUN_ENSEMBLE_SOLID_LIQUID_POWDER_HOST(template_parameters); } } else { if (rank == 0) std::cout << "Starting non-ensemble simulation" << std::endl; if (memory_space == "device") { if (n_material_states == 1) { RUN_SOLID_DEVICE(template_parameters); } else if (n_material_states == 2) { RUN_SOLID_LIQUID_DEVICE(template_parameters); } else { RUN_SOLID_LIQUID_POWDER_DEVICE(template_parameters); } } else { if (n_material_states == 1) { RUN_SOLID_HOST(template_parameters); } else if (n_material_states == 2) { RUN_SOLID_LIQUID_HOST(template_parameters); } else { RUN_SOLID_LIQUID_POWDER_HOST(template_parameters); } } } if (rank == 0) std::cout << "Simulation done" << std::endl; #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_END("main"); caliper_manager.flush(); #endif } catch (boost::bad_any_cast &exception) { std::cerr << std::endl; std::cerr << "Aborting." << std::endl; std::cerr << "Error: " << exception.what() << std::endl << std::endl; std::cerr << "There is a problem with the input file." << std::endl; std::cerr << "Make sure that the input file is correct" << std::endl; std::cerr << "and that you are using the following command" << std::endl; std::cerr << "to run adamantine:" << std::endl; std::cerr << "./adamantine --input-file=my_input_file" << std::endl; std::cerr << std::endl; } catch (std::exception &exception) { std::cerr << std::endl; std::cerr << "Aborting." << std::endl; std::cerr << "Error: " << exception.what() << std::endl; std::cerr << std::endl; return 1; } catch (...) { std::cerr << std::endl; std::cerr << "Aborting." << std::endl; std::cerr << "No error message." << std::endl; std::cerr << std::endl; return 1; } timers[adamantine::main].stop(); if (profiling == true) for (auto &timer : timers) timer.print(); #ifdef ADAMANTINE_WITH_ADIAK adiak::fini(); #endif return 0; }