diff --git a/CMakeLists.txt b/CMakeLists.txt index 2155524..352a9ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ option(WITH_NAN_PROTECTION "Guard against NaN/Inf in macroscopic variables" ON) option(USE_FLOAT "Use float instead of double as real_type" OFF) option(FORCE_USE_CPU "Force host execution even when a GPU is available" OFF) option(BENCHMARK_MODE "Build the benchmark driver" OFF) -option(PORTLBM_BUILD_EXECUTABLE "Build the run_portlbm driver executable" ON) +option(PORTLBM_BUILD_EXECUTABLE "Build the portlbm driver executable" ON) option(PORTLBM_BUILD_TESTS "Build the Catch2 unit and integration tests" OFF) option(PORTLBM_FORMAT_ONLY "Configure formatting targets only — skips all dependencies and compilation" OFF) @@ -152,7 +152,7 @@ if(WITH_VISUALIZATION) FetchContent_Declare( glfw GIT_REPOSITORY https://github.com/glfw/glfw.git - GIT_TAG 3.4 + GIT_TAG 3.3.8 GIT_SHALLOW TRUE) set(GLFW_BUILD_DOCS OFF @@ -231,17 +231,16 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/include/lbm/version.hpp.in" # ---- Executable target ----------------------------------------------------- if(PORTLBM_BUILD_EXECUTABLE) - add_executable(run_portlbm src/main_gpu.cpp) - target_link_libraries(run_portlbm PRIVATE portlbm_core) + add_executable(portlbm src/main_gpu.cpp) + target_link_libraries(portlbm PRIVATE portlbm_core) # benchmark.cpp is application-level; not compiled into the library. if(BENCHMARK_MODE) - target_sources(run_portlbm - PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/lbm/benchmark/benchmark.cpp") - target_compile_definitions(run_portlbm PRIVATE BENCHMARK_MODE) + target_sources(portlbm PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/lbm/benchmark/benchmark.cpp") + target_compile_definitions(portlbm PRIVATE BENCHMARK_MODE) endif() - add_sycl_to_target(TARGET run_portlbm) + add_sycl_to_target(TARGET portlbm) endif() # ---- Tests ----------------------------------------------------------------- diff --git a/README.md b/README.md index f28acd7..c9cf62c 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,16 @@ PortLBM is a GPU-accelerated 2D fluid simulator based on the Lattice Boltzmann M Only two dependencies must be installed manually. Everything else is either auto-fetched by CMake or installed via the provided script. -| Software | Version | How to obtain | -| -------- | ------- | ------------- | -| [llvm](https://github.com/llvm/llvm-project) | 18.1.8 | `spack install llvm@18.1.8 +llvm_dylib -gold +clang` | -| [glfw](https://github.com/glfw/glfw) | any | Only required with `-DWITH_VISUALIZATION=ON`. Auto-fetched by CMake if not found, but requires X11 dev headers (`sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev`). Alternatively: `spack install glfw` | +| Software | Version | Manual install | Spack command | +| -------- | ------- | :------------: | -------------- | +| [llvm](https://github.com/llvm/llvm-project) | 18.1.8 | yes | `spack install llvm@18.1.8 +llvm_dylib -gold +clang` | +| [boost](https://www.boost.org) | 1.85.0 | yes | `spack install boost@1.85.0 +fiber +context +atomic +filesystem` | +| [AdaptiveCpp](https://github.com/AdaptiveCpp/AdaptiveCpp) | v24.10 | no | — | +| [glfw](https://github.com/glfw/glfw) | 3.3.8 | no | `spack install glfw@3.3.8` | > **Note:** Building LLVM with Spack can take more than one hour depending on your machine. -[nlohmann/json](https://github.com/nlohmann/json) and [fmt](https://github.com/fmtlib/fmt) are fetched automatically by CMake if not found on the system. +AdaptiveCpp is installed automatically by the provided `install_adaptivecpp.sh` script (see below). GLFW is auto-fetched by CMake if not found on the system, but requires X11 dev headers (`sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev`); alternatively install via Spack. [nlohmann/json](https://github.com/nlohmann/json) and [fmt](https://github.com/fmtlib/fmt) are also fetched automatically by CMake. ### Installing AdaptiveCpp @@ -52,12 +54,22 @@ CMake automatically picks up the installation from the build directory — no ad # Without GUI cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DWITH_NAN_PROTECTION=OFF cmake --build build -./build/run_portlbm # With GUI cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DWITH_VISUALIZATION=ON -DWITH_NAN_PROTECTION=ON cmake --build build -./build/run_portlbm +``` + +### Running + +The executable locates `settings/settings.json` relative to itself, so it can be invoked from any working directory: + +```bash +./build/portlbm # from the project root +/absolute/path/to/build/portlbm # absolute path + +# Use a custom settings file +./portlbm /path/to/settings.json ``` ### Running the tests @@ -77,12 +89,12 @@ ctest --test-dir build --output-on-failure | `-DUSE_FLOAT` | `OFF` | Use `float` instead of `double` as `real_type`. Changes the ABI — all targets must agree. | | `-DFORCE_USE_CPU` | `OFF` | Use the CPU even when a faster device is available. | | `-DBENCHMARK_MODE` | `OFF` | Enable the benchmark driver (requires `PORTLBM_BUILD_EXECUTABLE=ON`). | -| `-DPORTLBM_BUILD_EXECUTABLE` | `ON` | Build the `run_portlbm` driver executable. | +| `-DPORTLBM_BUILD_EXECUTABLE` | `ON` | Build the `portlbm` driver executable. | | `-DPORTLBM_BUILD_TESTS` | `OFF` | Build the Catch2 unit and integration tests. | ## Simulation settings -Settings for the `run_portlbm` executable are read from `settings/settings.json`. A complete file looks like this: +Settings for the `portlbm` executable are read from `settings/settings.json`. A complete file looks like this: ```json { diff --git a/clang-format.patch b/clang-format.patch new file mode 100644 index 0000000..a471044 --- /dev/null +++ b/clang-format.patch @@ -0,0 +1,76 @@ +diff --git a/include/lbm/gui/lbm_gui.hpp b/include/lbm/gui/lbm_gui.hpp +index 6a40f8c..064dfec 100644 +--- a/include/lbm/gui/lbm_gui.hpp ++++ b/include/lbm/gui/lbm_gui.hpp +@@ -1077,7 +1077,9 @@ class Gui + ImPlot::PushColormap(colormaps->density_colormap); + + if (refit_plots) ++ { + ImPlot::SetNextAxesToFit(); ++ } + + if (ImPlot::BeginPlot( + "Density", +@@ -1169,8 +1171,7 @@ class Gui + if (windows->show_velocity && windows->enable_live_visualization) + { + ImGui::SetNextWindowPos( +- ImVec2((1.0 / 4) * monitor->viewport->WorkSize.x, windows->menu_bar_size), +- ImGuiCond_Appearing); ++ ImVec2((1.0 / 4) * monitor->viewport->WorkSize.x, windows->menu_bar_size), ImGuiCond_Appearing); + + ImGui::SetNextWindowSize( + ImVec2((3.0 / 4) * monitor->viewport->WorkSize.x, (1.0 / 2) * monitor->viewport->WorkSize.y), +@@ -1200,7 +1201,9 @@ class Gui + ImPlot::PushColormap(colormaps->velocity_colormap); + + if (refit_plots) ++ { + ImPlot::SetNextAxesToFit(); ++ } + + if (ImPlot::BeginPlot( + "Velocity", +diff --git a/src/lbm/benchmark/benchmark.cpp b/src/lbm/benchmark/benchmark.cpp +index fa391ab..fb08f1a 100644 +--- a/src/lbm/benchmark/benchmark.cpp ++++ b/src/lbm/benchmark/benchmark.cpp +@@ -249,10 +249,10 @@ void lbm::benchmark::Benchmark::phase_0_and_1_helper(int phase) + properties->algorithm = algorithm; + + // Create JSON file for algorithm +- std::string ofdir = (project_root_ / "benchmarks" / ("phase" + std::to_string(phase)) +- / ("results_phase" + std::to_string(phase) + "_" + std::string{algorithm} + "_" +- + device_name + ".json")) +- .string(); ++ std::string ofdir = ++ (project_root_ / "benchmarks" / ("phase" + std::to_string(phase)) ++ / ("results_phase" + std::to_string(phase) + "_" + std::string{ algorithm } + "_" + device_name + ".json")) ++ .string(); + + nlohmann::json file_data = prepare_file_data_phase_0_and_1(algorithm); + +@@ -412,7 +412,7 @@ void lbm::benchmark::Benchmark::phase_2() + // Create JSON file for algorithm + std::string ofdir = + (project_root_ / "benchmarks" / "phase2" +- / ("phase2_" + std::string{algorithm} + "_" + properties->scenario + "_" + device_name + ".json")) ++ / ("phase2_" + std::string{ algorithm } + "_" + properties->scenario + "_" + device_name + ".json")) + .string(); + nlohmann::json file_data = prepare_file_data_phase_2(); + +@@ -434,10 +434,9 @@ void lbm::benchmark::Benchmark::phase_2() + lbm::file_interaction::properties_to_json(*properties); + + // Create JSON file for algorithm +- std::string ofdir = +- (project_root_ / "benchmarks" / "phase2" +- / ("phase2_lptl_" + properties->scenario + "_" + device_name + ".json")) +- .string(); ++ std::string ofdir = (project_root_ / "benchmarks" / "phase2" ++ / ("phase2_lptl_" + properties->scenario + "_" + device_name + ".json")) ++ .string(); + nlohmann::json file_data = prepare_file_data_phase_2_tl_linear(); + + phase_2_inner_loop(ofdir, file_data, progress_phase); diff --git a/cmake-format.patch b/cmake-format.patch new file mode 100644 index 0000000..034f983 --- /dev/null +++ b/cmake-format.patch @@ -0,0 +1,90 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 10b55c8..352a9ad 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -236,8 +236,7 @@ if(PORTLBM_BUILD_EXECUTABLE) + + # benchmark.cpp is application-level; not compiled into the library. + if(BENCHMARK_MODE) +- target_sources(portlbm +- PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/lbm/benchmark/benchmark.cpp") ++ target_sources(portlbm PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/lbm/benchmark/benchmark.cpp") + target_compile_definitions(portlbm PRIVATE BENCHMARK_MODE) + endif() + +diff --git a/include/lbm/gui/lbm_gui.hpp b/include/lbm/gui/lbm_gui.hpp +index 6a40f8c..064dfec 100644 +--- a/include/lbm/gui/lbm_gui.hpp ++++ b/include/lbm/gui/lbm_gui.hpp +@@ -1077,7 +1077,9 @@ class Gui + ImPlot::PushColormap(colormaps->density_colormap); + + if (refit_plots) ++ { + ImPlot::SetNextAxesToFit(); ++ } + + if (ImPlot::BeginPlot( + "Density", +@@ -1169,8 +1171,7 @@ class Gui + if (windows->show_velocity && windows->enable_live_visualization) + { + ImGui::SetNextWindowPos( +- ImVec2((1.0 / 4) * monitor->viewport->WorkSize.x, windows->menu_bar_size), +- ImGuiCond_Appearing); ++ ImVec2((1.0 / 4) * monitor->viewport->WorkSize.x, windows->menu_bar_size), ImGuiCond_Appearing); + + ImGui::SetNextWindowSize( + ImVec2((3.0 / 4) * monitor->viewport->WorkSize.x, (1.0 / 2) * monitor->viewport->WorkSize.y), +@@ -1200,7 +1201,9 @@ class Gui + ImPlot::PushColormap(colormaps->velocity_colormap); + + if (refit_plots) ++ { + ImPlot::SetNextAxesToFit(); ++ } + + if (ImPlot::BeginPlot( + "Velocity", +diff --git a/src/lbm/benchmark/benchmark.cpp b/src/lbm/benchmark/benchmark.cpp +index fa391ab..fb08f1a 100644 +--- a/src/lbm/benchmark/benchmark.cpp ++++ b/src/lbm/benchmark/benchmark.cpp +@@ -249,10 +249,10 @@ void lbm::benchmark::Benchmark::phase_0_and_1_helper(int phase) + properties->algorithm = algorithm; + + // Create JSON file for algorithm +- std::string ofdir = (project_root_ / "benchmarks" / ("phase" + std::to_string(phase)) +- / ("results_phase" + std::to_string(phase) + "_" + std::string{algorithm} + "_" +- + device_name + ".json")) +- .string(); ++ std::string ofdir = ++ (project_root_ / "benchmarks" / ("phase" + std::to_string(phase)) ++ / ("results_phase" + std::to_string(phase) + "_" + std::string{ algorithm } + "_" + device_name + ".json")) ++ .string(); + + nlohmann::json file_data = prepare_file_data_phase_0_and_1(algorithm); + +@@ -412,7 +412,7 @@ void lbm::benchmark::Benchmark::phase_2() + // Create JSON file for algorithm + std::string ofdir = + (project_root_ / "benchmarks" / "phase2" +- / ("phase2_" + std::string{algorithm} + "_" + properties->scenario + "_" + device_name + ".json")) ++ / ("phase2_" + std::string{ algorithm } + "_" + properties->scenario + "_" + device_name + ".json")) + .string(); + nlohmann::json file_data = prepare_file_data_phase_2(); + +@@ -434,10 +434,9 @@ void lbm::benchmark::Benchmark::phase_2() + lbm::file_interaction::properties_to_json(*properties); + + // Create JSON file for algorithm +- std::string ofdir = +- (project_root_ / "benchmarks" / "phase2" +- / ("phase2_lptl_" + properties->scenario + "_" + device_name + ".json")) +- .string(); ++ std::string ofdir = (project_root_ / "benchmarks" / "phase2" ++ / ("phase2_lptl_" + properties->scenario + "_" + device_name + ".json")) ++ .string(); + nlohmann::json file_data = prepare_file_data_phase_2_tl_linear(); + + phase_2_inner_loop(ofdir, file_data, progress_phase); diff --git a/include/lbm/benchmark/benchmark.hpp b/include/lbm/benchmark/benchmark.hpp index 13a93bd..5c5e93a 100644 --- a/include/lbm/benchmark/benchmark.hpp +++ b/include/lbm/benchmark/benchmark.hpp @@ -20,6 +20,7 @@ // Standard library #include +#include #include #include #include @@ -81,6 +82,9 @@ class Benchmark private: std::shared_ptr sycl_algorithm_handler; + // Project root derived from the settings path — used for all relative I/O. + std::filesystem::path project_root_; + std::string device_name; std::unique_ptr properties; diff --git a/include/lbm/gui/lbm_gui.hpp b/include/lbm/gui/lbm_gui.hpp index ae79371..064dfec 100644 --- a/include/lbm/gui/lbm_gui.hpp +++ b/include/lbm/gui/lbm_gui.hpp @@ -21,6 +21,7 @@ #include "gui_constants.hpp" // Standard library +#include #include // ImGui @@ -270,6 +271,7 @@ class Gui // //////////////////////////////////////////////////////////////////////////////////////////////////////////// std::string settings_path_; + std::filesystem::path project_root_; std::unique_ptr properties_gui; std::unique_ptr windows; @@ -290,6 +292,9 @@ class Gui bool properties_changed; + // Set to true when the domain changes so both plots refit their axes on the next frame. + bool refit_plots; + double fps_update_time; // STYLES @@ -369,6 +374,7 @@ class Gui properties_changed = false; algorithm_handler->pause(); algorithm_handler->initialize(); + refit_plots = true; colormaps->density_colormap_lower_scale = std::min({ algorithm_handler->get_inlet_density(), algorithm_handler->get_outlet_density() }); @@ -1070,6 +1076,11 @@ class Gui ImPlot::PushColormap(colormaps->density_colormap); + if (refit_plots) + { + ImPlot::SetNextAxesToFit(); + } + if (ImPlot::BeginPlot( "Density", ImVec2(ImGui::GetContentRegionAvail().x - monitor->monitor_x_scale * 100 @@ -1189,6 +1200,11 @@ class Gui ImPlot::PushColormap(colormaps->velocity_colormap); + if (refit_plots) + { + ImPlot::SetNextAxesToFit(); + } + if (ImPlot::BeginPlot( "Velocity", ImVec2(ImGui::GetContentRegionAvail().x - monitor->monitor_x_scale * 100 @@ -1348,6 +1364,7 @@ class Gui public: explicit Gui(const std::string &&window_title, const std::string &settings_path) : settings_path_(settings_path), + project_root_(std::filesystem::path(settings_path_).parent_path().parent_path()), simulation_control(std::make_unique()), progress(std::make_unique()), colormaps(std::make_unique()), @@ -1357,6 +1374,7 @@ class Gui velocity_quiver_data(std::make_unique(2 * properties_gui->domain_node_count)), window_title(std::make_unique(window_title)), properties_changed(false), + refit_plots(false), fps_buffer(std::make_unique(2000)), algorithm_handler(std::make_unique(settings_path_)) { @@ -1401,13 +1419,14 @@ class Gui ImGui_ImplGlfw_InitForOpenGL(glfw_window, true); ImGui_ImplOpenGL3_Init(glsl_version); - set_light_style(); + set_dark_style(); // IO ImGuiIO &io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + const std::string font_path = (project_root_ / "fonts" / "DroidSans.ttf").string(); io.Fonts->AddFontFromFileTTF( - "../fonts/DroidSans.ttf", 2 * sqrt(monitor->monitor_x_scale * monitor->monitor_x_scale) * 9.0f); + font_path.c_str(), 2 * sqrt(monitor->monitor_x_scale * monitor->monitor_x_scale) * 9.0f); // ImPlot style ImPlotStyle &implot_style = ImPlot::GetStyle(); @@ -1491,6 +1510,7 @@ class Gui density_window(); velocity_window(); + refit_plots = false; debug_message(); render(); diff --git a/settings/settings.json b/settings/settings.json index 7e884fb..97394e6 100644 --- a/settings/settings.json +++ b/settings/settings.json @@ -1,19 +1,20 @@ { "algorithmic": { - "algorithm": "nptl", - "dataLayout": "stream", + "algorithm": "lptl", + "dataLayout": "bundle", "debugMode": false, - "frameUpdateInterval": 10001, - "timeSteps": 10000, + "frameUpdateInterval": 10, + "timeSteps": 1000000, "workGroupSize": 1024 }, "domain": { - "horizontalNodes": 500, - "scenario": "Hagen-Poiseuille", - "verticalNodes": 1000 + "horizontalNodes": 512, + "verticalNodes": 128, + "scenario": "circle" + }, "physical": { - "inletDensity": 1.2999999523162842, + "inletDensity": 1.2, "inletVelocity": { "x": 0.0, "y": 0.0 @@ -23,6 +24,6 @@ "x": 0.0, "y": 0.0 }, - "relaxationTime": 0.6000000238418579 + "relaxationTime": 0.6 } -} \ No newline at end of file +} diff --git a/src/lbm/benchmark/benchmark.cpp b/src/lbm/benchmark/benchmark.cpp index 52620ed..fb08f1a 100644 --- a/src/lbm/benchmark/benchmark.cpp +++ b/src/lbm/benchmark/benchmark.cpp @@ -63,6 +63,7 @@ std::vector lbm::benchmark::Benchmark::get_work_group_sizes(size_t max_w lbm::benchmark::Benchmark::Benchmark(std::shared_ptr &sycl_algorithm_handler) : sycl_algorithm_handler(sycl_algorithm_handler), + project_root_(std::filesystem::path(sycl_algorithm_handler->get_settings_path()).parent_path().parent_path()), properties(std::make_unique( file_interaction::json_to_properties(sycl_algorithm_handler->get_settings_path(), -2))), runtime_timer(std::make_unique()), @@ -80,7 +81,7 @@ lbm::benchmark::Benchmark::Benchmark(std::shared_ptrscenario = "Hagen-Poiseuille"; // Read benchmark settings - std::ifstream benchmark_settings_file("../settings/benchmark.json"); + std::ifstream benchmark_settings_file((project_root_ / "settings" / "benchmark.json").string()); nlohmann::json benchmark_settings = nlohmann::json::parse(benchmark_settings_file); benchmark_settings_file.close(); @@ -248,8 +249,10 @@ void lbm::benchmark::Benchmark::phase_0_and_1_helper(int phase) properties->algorithm = algorithm; // Create JSON file for algorithm - std::string ofdir = "../benchmarks/phase" + std::to_string(phase) + "/results_phase" + std::to_string(phase) - + "_" + std::string{ algorithm } + "_" + device_name + ".json"; + std::string ofdir = + (project_root_ / "benchmarks" / ("phase" + std::to_string(phase)) + / ("results_phase" + std::to_string(phase) + "_" + std::string{ algorithm } + "_" + device_name + ".json")) + .string(); nlohmann::json file_data = prepare_file_data_phase_0_and_1(algorithm); @@ -266,8 +269,9 @@ void lbm::benchmark::Benchmark::phase_0_and_1_helper(int phase) if (test_linear_two_lattice) { properties->algorithm = "lptl"; - std::string ofdir = "../benchmarks/phase" + std::to_string(phase) + "/results_phase" + std::to_string(phase) - + "_lptl_" + device_name + ".json"; + std::string ofdir = (project_root_ / "benchmarks" / ("phase" + std::to_string(phase)) + / ("results_phase" + std::to_string(phase) + "_lptl_" + device_name + ".json")) + .string(); nlohmann::json file_data = prepare_file_data_phase_0_and_1_tl_linear(); phase_0_and_1_inner_loop(phase, 0, ofdir, file_data, progress_phase); @@ -406,8 +410,10 @@ void lbm::benchmark::Benchmark::phase_2() lbm::file_interaction::properties_to_json(*properties); // Create JSON file for algorithm - std::string ofdir = "../benchmarks/phase2/phase2_" + std::string{ algorithm } + "_" + properties->scenario - + "_" + device_name + ".json"; + std::string ofdir = + (project_root_ / "benchmarks" / "phase2" + / ("phase2_" + std::string{ algorithm } + "_" + properties->scenario + "_" + device_name + ".json")) + .string(); nlohmann::json file_data = prepare_file_data_phase_2(); phase_2_inner_loop(ofdir, file_data, progress_phase); @@ -428,8 +434,9 @@ void lbm::benchmark::Benchmark::phase_2() lbm::file_interaction::properties_to_json(*properties); // Create JSON file for algorithm - std::string ofdir = - "../benchmarks/phase2/phase2_lptl_" + properties->scenario + "_" + device_name + ".json"; + std::string ofdir = (project_root_ / "benchmarks" / "phase2" + / ("phase2_lptl_" + properties->scenario + "_" + device_name + ".json")) + .string(); nlohmann::json file_data = prepare_file_data_phase_2_tl_linear(); phase_2_inner_loop(ofdir, file_data, progress_phase); diff --git a/src/lbm/gui/lbm_gui.cpp b/src/lbm/gui/lbm_gui.cpp index 4c4b07f..6522161 100644 --- a/src/lbm/gui/lbm_gui.cpp +++ b/src/lbm/gui/lbm_gui.cpp @@ -39,7 +39,7 @@ lbm::gui::Colormaps::Colormaps() : density_colormap(ImPlotColormap_Viridis), density_colormap_lower_scale(0.0f), density_colormap_upper_scale(0.0f), - velocity_colormap(ImPlotColormap_Hot), + velocity_colormap(ImPlotColormap_Jet), velocity_colormap_lower_scale(0.0), velocity_colormap_upper_scale(0.0f){}; diff --git a/src/main_gpu.cpp b/src/main_gpu.cpp index 79828b8..d632dcd 100644 --- a/src/main_gpu.cpp +++ b/src/main_gpu.cpp @@ -1,5 +1,5 @@ /** - * @brief Entry point of the parallel_lbm driver executable. + * @brief Entry point of the portlbm driver executable. * * @copyright Copyright (c) 2025 Marcel Graf * Copyright (c) 2026 Alexander Strack @@ -9,11 +9,16 @@ #include #include +#include + +#ifdef __linux__ +#include // readlink +#endif // Library public API -#include #include #include +#include #ifdef WITH_VISUALIZATION #include @@ -24,12 +29,74 @@ #endif // --------------------------------------------------------------------------- -// Helpers +// Path resolution // --------------------------------------------------------------------------- -// The settings file is located relative to the executable by convention. -// Application code owns this path — the library never hard-codes it. -static const std::string SETTINGS_PATH = "../settings/settings.json"; +/** + * @brief Returns the directory that contains the running executable. + * Uses /proc/self/exe on Linux for reliability; falls back to + * std::filesystem::canonical(argv[0]) on other platforms. + */ +static std::filesystem::path exe_dir(const char* argv0) +{ + namespace fs = std::filesystem; +#ifdef __linux__ + char buf[4096]; + ssize_t len = ::readlink("/proc/self/exe", buf, sizeof(buf) - 1); + if (len > 0) + { + buf[len] = '\0'; + return fs::path(buf).parent_path(); + } +#endif + std::error_code ec; + fs::path p = fs::canonical(argv0, ec); + if (!ec) + return p.parent_path(); + return fs::current_path(); +} + +/** + * @brief Returns the project root directory — the parent of the directory + * that contains the executable (development layout: exe is in build/). + * Falls back to the exe's own directory if settings are found there + * instead (in-source or installed layout). + */ +static std::filesystem::path project_root(const char* argv0) +{ + namespace fs = std::filesystem; + const fs::path dir = exe_dir(argv0); + + // Out-of-source build: /build/portlbm => root = dir/.. + if (fs::exists(dir.parent_path() / "settings" / "settings.json")) + return dir.parent_path(); + // In-source or installed: /portlbm => root = dir + if (fs::exists(dir / "settings" / "settings.json")) + return dir; + + return dir.parent_path(); // best guess; caller will report a clear error +} + +/** + * @brief Resolves the path to settings.json. + * If argv[1] is provided it is used directly; otherwise the file is + * located relative to the executable so the binary can be invoked + * from any working directory. + * + * @throws std::runtime_error when no settings file can be found. + */ +static std::string resolve_settings(int argc, const char* const* argv) +{ + if (argc > 1) + return argv[1]; + + const std::filesystem::path candidate = project_root(argv[0]) / "settings" / "settings.json"; + if (!std::filesystem::exists(candidate)) + throw std::runtime_error( + "Cannot locate settings/settings.json relative to the executable.\n" + "Usage: portlbm [/path/to/settings.json]"); + return candidate.string(); +} // --------------------------------------------------------------------------- // main() — four variants controlled by preprocessor flags @@ -37,97 +104,105 @@ static const std::string SETTINGS_PATH = "../settings/settings.json"; #ifdef WITH_VISUALIZATION - #ifdef BENCHMARK_MODE // WITH_VISUALIZATION && BENCHMARK_MODE +#ifdef BENCHMARK_MODE // WITH_VISUALIZATION && BENCHMARK_MODE + +int main(int argc, char* argv[]) +{ + try + { + const std::string settings_path = resolve_settings(argc, argv); + const std::filesystem::path root = project_root(argv[0]); - int main(int argc, char* argv[]) + std::filesystem::create_directories(root / "benchmarks" / "gui"); + + lbm::gui::Gui gui("PortLBM", settings_path); + gui.run(); + } + catch (const std::exception& e) { - std::filesystem::create_directories("../benchmarks/gui"); - - try - { - lbm::gui::Gui gui("SYCL Lattice Boltzmann", SETTINGS_PATH); - gui.run(); - } - catch(const lbm::exceptions::Exception &exception) - { - std::cerr << exception.to_string(); - } - - return 0; + std::cerr << e.what() << "\n"; + return 1; } + return 0; +} - #else // WITH_VISUALIZATION && !BENCHMARK_MODE +#else // WITH_VISUALIZATION && !BENCHMARK_MODE - int main(int argc, char* argv[]) +int main(int argc, char* argv[]) +{ + try { - try - { - lbm::gui::Gui gui("SYCL Lattice Boltzmann", SETTINGS_PATH); - gui.run(); - } - catch(const lbm::exceptions::Exception &exception) - { - std::cerr << exception.to_string(); - } - - return 0; + const std::string settings_path = resolve_settings(argc, argv); + lbm::gui::Gui gui("PortLBM", settings_path); + gui.run(); } + catch (const std::exception& e) + { + std::cerr << e.what() << "\n"; + return 1; + } + return 0; +} - #endif +#endif -#else // !WITH_VISUALIZATION +#else // !WITH_VISUALIZATION - #ifdef BENCHMARK_MODE // !WITH_VISUALIZATION && BENCHMARK_MODE +#ifdef BENCHMARK_MODE // !WITH_VISUALIZATION && BENCHMARK_MODE - int main(int argc, char* argv[]) +int main(int argc, char* argv[]) +{ + try { - std::filesystem::create_directories("../benchmarks/phase0"); - std::filesystem::create_directories("../benchmarks/phase1"); - std::filesystem::create_directories("../benchmarks/phase2"); - - try - { - std::cout << "Starting benchmark. \n\n"; - - std::shared_ptr algorithm_handler = - std::make_shared(SETTINGS_PATH); - - lbm::benchmark::Benchmark benchmark(algorithm_handler); - benchmark.phase_0(); - benchmark.phase_1(); - benchmark.phase_2(); - - std::cout << "\nDone with all benchmarks. Exiting. \n\n"; - } - catch(const lbm::exceptions::Exception &exception) - { - std::cerr << exception.to_string(); - } - - return 0; + const std::string settings_path = resolve_settings(argc, argv); + const std::filesystem::path root = project_root(argv[0]); + + std::filesystem::create_directories(root / "benchmarks" / "phase0"); + std::filesystem::create_directories(root / "benchmarks" / "phase1"); + std::filesystem::create_directories(root / "benchmarks" / "phase2"); + + std::cout << "Starting benchmark.\n\n"; + + std::shared_ptr algorithm_handler = + std::make_shared(settings_path); + + lbm::benchmark::Benchmark benchmark(algorithm_handler); + benchmark.phase_0(); + benchmark.phase_1(); + benchmark.phase_2(); + + std::cout << "\nDone with all benchmarks. Exiting.\n\n"; } + catch (const std::exception& e) + { + std::cerr << e.what() << "\n"; + return 1; + } + return 0; +} - #else // !WITH_VISUALIZATION && !BENCHMARK_MODE +#else // !WITH_VISUALIZATION && !BENCHMARK_MODE - int main(int argc, char* argv[]) +int main(int argc, char* argv[]) +{ + try + { + const std::string settings_path = resolve_settings(argc, argv); + std::unique_ptr algorithm_handler = + std::make_unique(settings_path); + algorithm_handler->start(); + algorithm_handler->block_until_finished(); + } + catch (const std::exception& e) { - try - { - std::unique_ptr algorithm_handler = - std::make_unique(SETTINGS_PATH); - algorithm_handler->start(); - algorithm_handler->block_until_finished(); - } - catch(const lbm::exceptions::Exception &exception) - { - std::cerr << exception.to_string(); - } - - return 0; + std::cerr << e.what() << "\n"; + return 1; } + return 0; +} - #endif // !BENCHMARK_MODE +#endif // !BENCHMARK_MODE -#endif // !WITH_VISUALIZATION +#endif // !WITH_VISUALIZATION // ! main_gpu.cpp diff --git a/tests/test_properties.cpp b/tests/test_properties.cpp index 041994f..9798133 100644 --- a/tests/test_properties.cpp +++ b/tests/test_properties.cpp @@ -220,11 +220,11 @@ TEST_CASE("json_to_properties: parses bundled settings/settings.json", lbm::core::Properties p = lbm::file_interaction::json_to_properties(path); - CHECK(p.algorithm == "nptl"); - CHECK(p.data_layout == "stream"); - CHECK(p.scenario == "Hagen-Poiseuille"); + CHECK(p.algorithm == "lptl"); + CHECK(p.data_layout == "bundle"); + CHECK(p.scenario == "circle"); CHECK(p.debug_mode == false); - CHECK(p.time_steps == 10000u); + CHECK(p.time_steps == 1000000u); CHECK(p.work_group_size == 1024u); CHECK(p.inlet_density > 0.0); CHECK(p.outlet_density > 0.0);