diff --git a/README.md b/README.md index 271e27c249..7b50af7321 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build application](https://github.com/learning-process/ppc-2025-processes-informatics/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/learning-process/ppc-2025-processes-informatics/actions/workflows/main.yml) -[![Pages](https://github.com/learning-process/ppc-2025-processes-informatics/actions/workflows/pages.yml/badge.svg?branch=master)](https://github.com/learning-process/ppc-2025-processes-informatics/actions/workflows/pages.yml) +[![Pages](https://github.com/learning-process/parallel_programming_course/actions/workflows/pages.yml/badge.svg?branch=master)](https://github.com/learning-process/parallel_programming_course/actions/workflows/pages.yml) [![CodeQL](https://github.com/learning-process/ppc-2025-processes-informatics/actions/workflows/codeql.yml/badge.svg?branch=master)](https://github.com/learning-process/ppc-2025-processes-informatics/actions/workflows/codeql.yml) [![codecov](https://codecov.io/gh/learning-process/ppc-2025-processes-informatics/graph/badge.svg?token=qCOtqeFyIz)](https://codecov.io/gh/learning-process/ppc-2025-processes-informatics) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/learning-process/ppc-2025-processes-informatics/badge)](https://scorecard.dev/viewer/?uri=github.com/learning-process/ppc-2025-processes-informatics) diff --git a/cmake/gtest.cmake b/cmake/gtest.cmake index aa56249858..7f0f7d441c 100644 --- a/cmake/gtest.cmake +++ b/cmake/gtest.cmake @@ -19,12 +19,12 @@ ExternalProject_Add( $<$:-Dgtest_force_shared_crt=ON> BUILD_COMMAND "${CMAKE_COMMAND}" --build - "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/build" --config - ${CMAKE_BUILD_TYPE} --parallel + "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/build" --config $ + --parallel INSTALL_COMMAND "${CMAKE_COMMAND}" --install - "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/build" --prefix - "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/install") + "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/build" --config $ + --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/install") function(ppc_link_gtest exec_func_lib) # Add external project include directories diff --git a/cmake/json.cmake b/cmake/json.cmake index e7cb80c178..30706b1563 100644 --- a/cmake/json.cmake +++ b/cmake/json.cmake @@ -16,10 +16,10 @@ ExternalProject_Add( -DJSON_BuildTests=OFF BUILD_COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/build" - --config ${CMAKE_BUILD_TYPE} --parallel + --config $ --parallel INSTALL_COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/build" - --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/install") + --config $ --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/install") function(ppc_link_json exec_func_lib) # Add external project include directories diff --git a/cmake/libenvpp.cmake b/cmake/libenvpp.cmake index 0d3adc23b4..c7217a8475 100644 --- a/cmake/libenvpp.cmake +++ b/cmake/libenvpp.cmake @@ -15,11 +15,11 @@ ExternalProject_Add( -DLIBENVPP_EXAMPLES=OFF BUILD_COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_CURRENT_BINARY_DIR}/ppc_libenvpp/build" - --config ${CMAKE_BUILD_TYPE} --parallel + --config $ --parallel INSTALL_COMMAND "${CMAKE_COMMAND}" --install - "${CMAKE_CURRENT_BINARY_DIR}/ppc_libenvpp/build" --prefix - "${CMAKE_CURRENT_BINARY_DIR}/ppc_libenvpp/install") + "${CMAKE_CURRENT_BINARY_DIR}/ppc_libenvpp/build" --config $ + --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_libenvpp/install") string(TOLOWER "${CMAKE_BUILD_TYPE}" cmake_build_type_lower) if(cmake_build_type_lower STREQUAL "debug") @@ -48,5 +48,6 @@ function(ppc_link_envpp exec_func_lib) target_link_directories(${exec_func_lib} PUBLIC "${CMAKE_BINARY_DIR}/ppc_libenvpp/build") target_link_libraries(${exec_func_lib} PUBLIC ${PPC_ENVPP_LIB_NAME}) - target_link_libraries(${exec_func_lib} PUBLIC ${PPC_FMT_LIB_NAME}) + target_link_libraries(${exec_func_lib} PUBLIC $<$:fmtd> + $<$>:fmt>) endfunction() diff --git a/cmake/onetbb.cmake b/cmake/onetbb.cmake index c8b1a1a674..08ef716c90 100644 --- a/cmake/onetbb.cmake +++ b/cmake/onetbb.cmake @@ -29,10 +29,10 @@ if(NOT ENABLE_SYSTEM_TBB) -DTBB_TEST=OFF BUILD_COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_CURRENT_BINARY_DIR}/ppc_onetbb/build" - --config ${CMAKE_BUILD_TYPE} --parallel + --config $ --parallel INSTALL_COMMAND "${CMAKE_COMMAND}" --install - "${CMAKE_CURRENT_BINARY_DIR}/ppc_onetbb/build" --prefix + "${CMAKE_CURRENT_BINARY_DIR}/ppc_onetbb/build" --config $ --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_onetbb/install" TEST_COMMAND ${ppc_onetbb_TEST_COMMAND}) diff --git a/modules/runners/src/runners.cpp b/modules/runners/src/runners.cpp index e138528749..5ab4e8e6b5 100644 --- a/modules/runners/src/runners.cpp +++ b/modules/runners/src/runners.cpp @@ -3,12 +3,17 @@ #include #include +#include +#include #include +#include #include #include #include +#include #include #include +#include #include "oneapi/tbb/global_control.h" #include "util/include/util.hpp" @@ -48,6 +53,8 @@ void WorkerTestFailurePrinter::OnTestEnd(const ::testing::TestInfo &test_info) { } PrintProcessRank(); base_->OnTestEnd(test_info); + // Abort the whole MPI job on any test failure to avoid other ranks hanging on barriers. + MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); } void WorkerTestFailurePrinter::OnTestPartResult(const ::testing::TestPartResult &test_part_result) { @@ -73,6 +80,63 @@ int RunAllTests() { } return status; } + +void SyncGTestSeed() { + unsigned int seed = 0; + int rank = -1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + try { + seed = std::random_device{}(); + } catch (...) { + seed = 0; + } + if (seed == 0) { + const auto now = static_cast(std::chrono::steady_clock::now().time_since_epoch().count()); + seed = static_cast(((now & 0x7fffffffULL) | 1ULL)); + } + } + MPI_Bcast(&seed, 1, MPI_UNSIGNED, 0, MPI_COMM_WORLD); + ::testing::GTEST_FLAG(random_seed) = static_cast(seed); +} + +void SyncGTestFilter() { + int rank = -1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + std::string filter = (rank == 0) ? ::testing::GTEST_FLAG(filter) : std::string{}; + int len = static_cast(filter.size()); + MPI_Bcast(&len, 1, MPI_INT, 0, MPI_COMM_WORLD); + if (rank != 0) { + filter.resize(static_cast(len)); + } + if (len > 0) { + MPI_Bcast(filter.data(), len, MPI_CHAR, 0, MPI_COMM_WORLD); + } + ::testing::GTEST_FLAG(filter) = filter; +} + +bool HasFlag(int argc, char **argv, std::string_view flag) { + for (int i = 1; i < argc; ++i) { + if (argv[i] != nullptr && std::string_view(argv[i]) == flag) { + return true; + } + } + return false; +} + +int RunAllTestsSafely() { + try { + return RunAllTests(); + } catch (const std::exception &e) { + std::cerr << std::format("[ ERROR ] Exception after tests: {}", e.what()) << '\n'; + MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); + return EXIT_FAILURE; + } catch (...) { + std::cerr << "[ ERROR ] Unknown exception after tests" << '\n'; + MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); + return EXIT_FAILURE; + } +} } // namespace int Init(int argc, char **argv) { @@ -88,16 +152,21 @@ int Init(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); + // Synchronize GoogleTest internals across ranks to avoid divergence + SyncGTestSeed(); + SyncGTestFilter(); + auto &listeners = ::testing::UnitTest::GetInstance()->listeners(); int rank = -1; MPI_Comm_rank(MPI_COMM_WORLD, &rank); - if (rank != 0 && (argc < 2 || argv[1] != std::string("--print-workers"))) { + const bool print_workers = HasFlag(argc, argv, "--print-workers"); + if (rank != 0 && !print_workers) { auto *listener = listeners.Release(listeners.default_result_printer()); listeners.Append(new WorkerTestFailurePrinter(std::shared_ptr<::testing::TestEventListener>(listener))); } listeners.Append(new UnreadMessagesDetector()); - auto status = RunAllTests(); + const int status = RunAllTestsSafely(); const int finalize_res = MPI_Finalize(); if (finalize_res != MPI_SUCCESS) { diff --git a/modules/util/include/func_test_util.hpp b/modules/util/include/func_test_util.hpp index 9a4e713272..6cae302886 100644 --- a/modules/util/include/func_test_util.hpp +++ b/modules/util/include/func_test_util.hpp @@ -61,6 +61,8 @@ class BaseRunFuncTests : public ::testing::TestWithParam +#include #include +#include #include #include +#include #include +#include #include +#include +#include #include #ifdef __GNUG__ # include @@ -17,6 +24,10 @@ # pragma warning(disable : 4459) #endif +#include + +#include +#include #include /// @brief JSON namespace used for settings and config parsing. @@ -90,4 +101,69 @@ inline std::shared_ptr InitJSONPtr() { bool IsUnderMpirun(); +namespace test { + +[[nodiscard]] inline std::string SanitizeToken(std::string_view token_sv) { + std::string token{token_sv}; + auto is_allowed = [](char c) { + return std::isalnum(static_cast(c)) || c == '_' || c == '-' || c == '.'; + }; + std::ranges::replace(token, ' ', '_'); + for (char &ch : token) { + if (!is_allowed(ch)) { + ch = '_'; + } + } + return token; +} + +class ScopedPerTestEnv { + public: + explicit ScopedPerTestEnv(const std::string &token) + : set_uid_("PPC_TEST_UID", token), set_tmp_("PPC_TEST_TMPDIR", CreateTmpDir(token)) {} + + private: + static std::string CreateTmpDir(const std::string &token) { + namespace fs = std::filesystem; + auto make_rank_suffix = []() -> std::string { + // Derive rank from common MPI env vars without including MPI headers + constexpr std::array kRankVars = {"OMPI_COMM_WORLD_RANK", "PMI_RANK", "PMIX_RANK", + "SLURM_PROCID", "MSMPI_RANK"}; + for (auto name : kRankVars) { + if (auto r = env::get(name); r.has_value() && r.value() >= 0) { + return std::string("_rank_") + std::to_string(r.value()); + } + } + return std::string{}; + }; + const std::string rank_suffix = IsUnderMpirun() ? make_rank_suffix() : std::string{}; + const fs::path tmp = fs::temp_directory_path() / (std::string("ppc_test_") + token + rank_suffix); + std::error_code ec; + fs::create_directories(tmp, ec); + (void)ec; + return tmp.string(); + } + + env::detail::set_scoped_environment_variable set_uid_; + env::detail::set_scoped_environment_variable set_tmp_; +}; + +[[nodiscard]] inline std::string MakeCurrentGTestToken(std::string_view fallback_name) { + const auto *unit = ::testing::UnitTest::GetInstance(); + const auto *info = (unit != nullptr) ? unit->current_test_info() : nullptr; + std::ostringstream os; + if (info != nullptr) { + os << info->test_suite_name() << "." << info->name(); + } else { + os << fallback_name; + } + return SanitizeToken(os.str()); +} + +inline ScopedPerTestEnv MakePerTestEnvForCurrentGTest(std::string_view fallback_name) { + return ScopedPerTestEnv(MakeCurrentGTestToken(fallback_name)); +} + +} // namespace test + } // namespace ppc::util diff --git a/scoreboard/data/deadlines.yml b/scoreboard/data/deadlines.yml index 152422d2e2..338ac383f7 100644 --- a/scoreboard/data/deadlines.yml +++ b/scoreboard/data/deadlines.yml @@ -8,6 +8,6 @@ threads: processes: # Use integer offsets for tasks; default 0. - task_1: 0 - task_2: 0 - task_3: 0 + task_1: 17 + task_2: 17 + task_3: 17 diff --git a/scripts/run_tests.py b/scripts/run_tests.py index a6f75a75f3..f8477c56b6 100755 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -53,6 +53,17 @@ def __init__(self, verbose=False): self.mpi_exec = "mpiexec" else: self.mpi_exec = "mpirun" + self.platform = platform.system() + + # Detect MPI implementation to choose compatible flags + self.mpi_env_mode = "unknown" # one of: openmpi, mpich, unknown + self.mpi_np_flag = "-np" + if self.platform == "Windows": + # MSMPI uses -env and -n + self.mpi_env_mode = "mpich" + self.mpi_np_flag = "-n" + else: + self.mpi_env_mode, self.mpi_np_flag = self.__detect_mpi_impl() @staticmethod def __get_project_path(): @@ -88,6 +99,81 @@ def __run_exec(self, command): if result.returncode != 0: raise Exception(f"Subprocess return {result.returncode}.") + def __detect_mpi_impl(self): + """Detect MPI implementation and return (env_mode, np_flag). + env_mode: 'openmpi' -> use '-x VAR', 'mpich' -> use '-genvlist VAR1,VAR2', 'unknown' -> pass no env flags. + np_flag: '-np' for OpenMPI/unknown, '-n' for MPICH-family. + """ + probes = (["--version"], ["-V"], ["-v"], ["--help"], ["-help"]) + out = "" + for args in probes: + try: + proc = subprocess.run( + [self.mpi_exec] + list(args), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + out = (proc.stdout or "").lower() + if out: + break + except Exception: + continue + + if "open mpi" in out or "ompi" in out: + return "openmpi", "-np" + if ( + "hydra" in out + or "mpich" in out + or "intel(r) mpi" in out + or "intel mpi" in out + ): + return "mpich", "-n" + return "unknown", "-np" + + def __build_mpi_cmd(self, ppc_num_proc, additional_mpi_args): + base = [self.mpi_exec] + shlex.split(additional_mpi_args) + + if self.platform == "Windows": + # MS-MPI style + env_args = [ + "-env", + "PPC_NUM_THREADS", + self.__ppc_env["PPC_NUM_THREADS"], + "-env", + "OMP_NUM_THREADS", + self.__ppc_env["OMP_NUM_THREADS"], + ] + np_args = ["-n", ppc_num_proc] + return base + env_args + np_args + + # Non-Windows + if self.mpi_env_mode == "openmpi": + env_args = [ + "-x", + "PPC_NUM_THREADS", + "-x", + "OMP_NUM_THREADS", + ] + np_flag = "-np" + elif self.mpi_env_mode == "mpich": + # Explicitly set env variables for all ranks + env_args = [ + "-env", + "PPC_NUM_THREADS", + self.__ppc_env["PPC_NUM_THREADS"], + "-env", + "OMP_NUM_THREADS", + self.__ppc_env["OMP_NUM_THREADS"], + ] + np_flag = "-n" + else: + # Unknown MPI flavor: rely on environment inheritance and default to -np + env_args = [] + np_flag = "-np" + + return base + env_args + [np_flag, ppc_num_proc] + @staticmethod def __get_gtest_settings(repeats_count, type_task): command = [ @@ -133,32 +219,29 @@ def run_processes(self, additional_mpi_args): raise EnvironmentError( "Required environment variable 'PPC_NUM_PROC' is not set." ) - - mpi_running = ( - [self.mpi_exec] + shlex.split(additional_mpi_args) + ["-np", ppc_num_proc] - ) + mpi_running = self.__build_mpi_cmd(ppc_num_proc, additional_mpi_args) if not self.__ppc_env.get("PPC_ASAN_RUN"): for task_type in ["all", "mpi"]: self.__run_exec( mpi_running + [str(self.work_dir / "ppc_func_tests")] - + self.__get_gtest_settings(1, "_" + task_type) + + self.__get_gtest_settings(1, "_" + task_type + "_") ) def run_performance(self): if not self.__ppc_env.get("PPC_ASAN_RUN"): - mpi_running = [self.mpi_exec, "-np", self.__ppc_num_proc] + mpi_running = self.__build_mpi_cmd(self.__ppc_num_proc, "") for task_type in ["all", "mpi"]: self.__run_exec( mpi_running + [str(self.work_dir / "ppc_perf_tests")] - + self.__get_gtest_settings(1, "_" + task_type) + + self.__get_gtest_settings(1, "_" + task_type + "_") ) for task_type in ["omp", "seq", "stl", "tbb"]: self.__run_exec( [str(self.work_dir / "ppc_perf_tests")] - + self.__get_gtest_settings(1, "_" + task_type) + + self.__get_gtest_settings(1, "_" + task_type + "_") ) diff --git a/tasks/dorofeev_i_monte_carlo_integration/common/include/common.hpp b/tasks/dorofeev_i_monte_carlo_integration/common/include/common.hpp new file mode 100644 index 0000000000..268dc60213 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/common/include/common.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +struct InputData { + std::function &)> func; // f(x) + std::vector a; // lower bounds + std::vector b; // upper bounds + int samples; // number of samples +}; + +using InType = InputData; +using OutType = double; + +using TestType = std::tuple; // for testing purposes +using BaseTask = ppc::task::Task; + +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/dorofeev_i_monte_carlo_integration/data/pic.jpg b/tasks/dorofeev_i_monte_carlo_integration/data/pic.jpg new file mode 100644 index 0000000000..3445802349 Binary files /dev/null and b/tasks/dorofeev_i_monte_carlo_integration/data/pic.jpg differ diff --git a/tasks/dorofeev_i_monte_carlo_integration/info.json b/tasks/dorofeev_i_monte_carlo_integration/info.json new file mode 100644 index 0000000000..54b79b6d94 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Дорофеев", + "last_name": "Иван", + "middle_name": "Денисович", + "group_number": "3823Б1ФИ1", + "task_number": "1" + } +} diff --git a/tasks/dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp b/tasks/dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..ec93e73dba --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +class DorofeevIMonteCarloIntegrationMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit DorofeevIMonteCarloIntegrationMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/dorofeev_i_monte_carlo_integration/mpi/src/ops_mpi.cpp b/tasks/dorofeev_i_monte_carlo_integration/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..ed0ccaa870 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/mpi/src/ops_mpi.cpp @@ -0,0 +1,101 @@ +#include "dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp" + +#include + +#include + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +DorofeevIMonteCarloIntegrationMPI::DorofeevIMonteCarloIntegrationMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool DorofeevIMonteCarloIntegrationMPI::ValidationImpl() { + const auto &in = GetInput(); + + if (!in.func) { + return false; + } + if (in.a.size() == 0 || in.a.size() != in.b.size()) { + return false; + } + + for (size_t i = 0; i < in.a.size(); i++) { + if (in.b[i] <= in.a[i]) { + return false; + } + } + + if (in.samples <= 0) { + return false; + } + + return true; +} + +bool DorofeevIMonteCarloIntegrationMPI::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool DorofeevIMonteCarloIntegrationMPI::RunImpl() { + const auto in = GetInput(); + const int dims = in.a.size(); + const int N_total = in.samples; + + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int N_local = N_total / size; + if (rank == size - 1) { + N_local += N_total % size; + } + + std::vector> dist; + dist.reserve(dims); + for (int d = 0; d < dims; ++d) { + dist.emplace_back(in.a[d], in.b[d]); + } + + std::mt19937 gen(rank + 123); + double local_sum = 0.0; + + for (int i = 0; i < N_local; ++i) { + std::vector x(dims); + for (int d = 0; d < dims; d++) { + x[d] = dist[d](gen); + } + local_sum += in.func(x); + } + + double global_sum = 0.0; + + MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + double volume = 1.0; + for (int d = 0; d < dims; d++) { + volume *= (in.b[d] - in.a[d]); + } + + double result = 0.0; + if (rank == 0) { + result = (global_sum / N_total) * volume; + } + + MPI_Bcast(&result, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + GetOutput() = result; + return true; +} + +bool DorofeevIMonteCarloIntegrationMPI::PostProcessingImpl() { + return true; +} + +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/dorofeev_i_monte_carlo_integration/report.md b/tasks/dorofeev_i_monte_carlo_integration/report.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tasks/dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp b/tasks/dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..ac17406972 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +class DorofeevIMonteCarloIntegrationSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit DorofeevIMonteCarloIntegrationSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/dorofeev_i_monte_carlo_integration/seq/src/ops_seq.cpp b/tasks/dorofeev_i_monte_carlo_integration/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..51dc5d8bbe --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/seq/src/ops_seq.cpp @@ -0,0 +1,77 @@ +#include "dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp" + +#include + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +DorofeevIMonteCarloIntegrationSEQ::DorofeevIMonteCarloIntegrationSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0.0; +} + +bool DorofeevIMonteCarloIntegrationSEQ::ValidationImpl() { + const auto &in = GetInput(); + + if (in.a.size() != in.b.size()) { + return false; + } + + if (in.samples <= 0) { + return false; + } + + for (size_t i = 0; i < in.a.size(); i++) { + if (in.b[i] <= in.a[i]) { + return false; + } + } + + return true; +} + +bool DorofeevIMonteCarloIntegrationSEQ::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool DorofeevIMonteCarloIntegrationSEQ::RunImpl() { + const auto &in = GetInput(); + const int dims = in.a.size(); + const int N = in.samples; + + std::mt19937 gen(12345); + std::vector> dist; + dist.reserve(dims); + + for (int i = 0; i < dims; i++) { + dist.emplace_back(in.a[i], in.b[i]); + } + + double sum = 0.0; + std::vector point(dims); + + for (int s = 0; s < N; s++) { + for (int d = 0; d < dims; d++) { + point[d] = dist[d](gen); + } + sum += in.func(point); + } + + double volume = 1.0; + for (int i = 0; i < dims; i++) { + volume *= (in.b[i] - in.a[i]); + } + + GetOutput() = volume * (sum / N); + return true; +} + +bool DorofeevIMonteCarloIntegrationSEQ::PostProcessingImpl() { + return true; +} + +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/dorofeev_i_monte_carlo_integration/settings.json b/tasks/dorofeev_i_monte_carlo_integration/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/dorofeev_i_monte_carlo_integration/tests/.clang-tidy b/tasks/dorofeev_i_monte_carlo_integration/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/tests/.clang-tidy @@ -0,0 +1,13 @@ +InheritParentConfig: true + +Checks: > + -modernize-loop-convert, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -modernize-use-std-print, + -modernize-type-traits + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 50 # Relaxed for tests diff --git a/tasks/dorofeev_i_monte_carlo_integration/tests/functional/main.cpp b/tasks/dorofeev_i_monte_carlo_integration/tests/functional/main.cpp new file mode 100644 index 0000000000..c3ef32d51d --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/tests/functional/main.cpp @@ -0,0 +1,71 @@ +#include + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" +#include "dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp" +#include "dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +class MonteCarloFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam( + const testing::TestParamInfo> &info) { + const auto &full = info.param; + + const TestType &t = std::get<2>(full); + std::string size_name = std::get<1>(t); + + std::string task_name = std::get<1>(full); + + return task_name + "_" + size_name; + } + + protected: + void SetUp() override { + auto full_param = GetParam(); + TestType t = std::get<2>(full_param); + + int samples = std::get<0>(t); + + input_data_.a = {0.0}; + input_data_.b = {1.0}; + input_data_.samples = samples; + input_data_.func = [](const std::vector &x) { return x[0] * x[0]; }; + } + + bool CheckTestOutputData(OutType &output_data) final { + double expected = 1.0 / 3.0; + return std::abs(output_data - expected) < 0.05; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(MonteCarloFuncTests, IntegrationTest) { + ExecuteTest(GetParam()); +} + +const std::array kParams = { + std::make_tuple(1000, "small"), + std::make_tuple(5000, "medium"), + std::make_tuple(20000, "large"), +}; + +const auto kTaskList = std::tuple_cat( + ppc::util::AddFuncTask(kParams, PPC_SETTINGS_example_processes), + ppc::util::AddFuncTask(kParams, PPC_SETTINGS_example_processes)); + +INSTANTIATE_TEST_SUITE_P(IntegrationTests, MonteCarloFuncTests, ppc::util::ExpandToValues(kTaskList), + MonteCarloFuncTests::PrintTestParam); + +} // namespace +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/dorofeev_i_monte_carlo_integration/tests/performance/main.cpp b/tasks/dorofeev_i_monte_carlo_integration/tests/performance/main.cpp new file mode 100644 index 0000000000..96043b8d5c --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/tests/performance/main.cpp @@ -0,0 +1,58 @@ +#include + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" +#include "dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp" +#include "dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { +namespace { + +class MonteCarloPerfTests : public ppc::util::BaseRunPerfTests { + public: + MonteCarloPerfTests() = default; + + private: + // samples for performance test + static constexpr int kSamples = 190000; + + InType input_data_{}; + + void SetUp() override { + input_data_.a = {0.0}; + input_data_.b = {1.0}; + input_data_.samples = kSamples; + input_data_.func = [](const std::vector &x) { + return x[0] * x[0]; // integrating x^2 on [0,1] + }; + } + + bool CheckTestOutputData(OutType &output_data) final { + double expected = 1.0 / 3.0; + return std::abs(output_data - expected) < 0.05; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(MonteCarloPerfTests, PerfTestModes) { + ExecuteTest(GetParam()); +} + +// making performance probs: MPI + SEQ +const auto PerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_example_processes); + +// converting to GTest values +const auto GTestValues = ppc::util::TupleToGTestValues(PerfTasks); + +// test naming function +const auto TestName = MonteCarloPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(MonteCarloPerf, MonteCarloPerfTests, GTestValues, TestName); + +} // namespace +} // namespace dorofeev_i_monte_carlo_integration_processes diff --git a/tasks/example_processes/data/pic.jpg b/tasks/example_processes/data/pic.jpg index 3445802349..637624238c 100644 Binary files a/tasks/example_processes/data/pic.jpg and b/tasks/example_processes/data/pic.jpg differ diff --git a/tasks/example_processes/tests/functional/main.cpp b/tasks/example_processes/tests/functional/main.cpp index d3f5f37952..23e91e3fdb 100644 --- a/tasks/example_processes/tests/functional/main.cpp +++ b/tasks/example_processes/tests/functional/main.cpp @@ -32,13 +32,14 @@ class NesterovARunFuncTestsProcesses : public ppc::util::BaseRunFuncTests img; - // Read image + // Read image in RGB to ensure consistent channel count { std::string abs_path = ppc::util::GetAbsoluteTaskPath(PPC_ID_example_processes, "pic.jpg"); - auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, 0); + auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, STBI_rgb); if (data == nullptr) { throw std::runtime_error("Failed to load image: " + std::string(stbi_failure_reason())); } + channels = STBI_rgb; img = std::vector(data, data + (static_cast(width * height * channels))); stbi_image_free(data); if (std::cmp_not_equal(width, height)) { diff --git a/tasks/example_processes_2/data/pic.jpg b/tasks/example_processes_2/data/pic.jpg index 3445802349..637624238c 100644 Binary files a/tasks/example_processes_2/data/pic.jpg and b/tasks/example_processes_2/data/pic.jpg differ diff --git a/tasks/example_processes_2/tests/functional/main.cpp b/tasks/example_processes_2/tests/functional/main.cpp index ff3442fda4..dbe2b0c472 100644 --- a/tasks/example_processes_2/tests/functional/main.cpp +++ b/tasks/example_processes_2/tests/functional/main.cpp @@ -32,13 +32,14 @@ class NesterovARunFuncTestsProcesses2 : public ppc::util::BaseRunFuncTests img; - // Read image + // Read image in RGB to ensure consistent channel count { std::string abs_path = ppc::util::GetAbsoluteTaskPath(PPC_ID_example_processes_2, "pic.jpg"); - auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, 0); + auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, STBI_rgb); if (data == nullptr) { throw std::runtime_error("Failed to load image: " + std::string(stbi_failure_reason())); } + channels = STBI_rgb; img = std::vector(data, data + (static_cast(width * height * channels))); stbi_image_free(data); if (std::cmp_not_equal(width, height)) { diff --git a/tasks/example_processes_3/data/pic.jpg b/tasks/example_processes_3/data/pic.jpg index 3445802349..637624238c 100644 Binary files a/tasks/example_processes_3/data/pic.jpg and b/tasks/example_processes_3/data/pic.jpg differ diff --git a/tasks/example_processes_3/tests/functional/main.cpp b/tasks/example_processes_3/tests/functional/main.cpp index 99e5a5b15d..20a89ea793 100644 --- a/tasks/example_processes_3/tests/functional/main.cpp +++ b/tasks/example_processes_3/tests/functional/main.cpp @@ -32,13 +32,14 @@ class NesterovARunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests img; - // Read image + // Read image in RGB to ensure consistent channel count { std::string abs_path = ppc::util::GetAbsoluteTaskPath(PPC_ID_example_processes_3, "pic.jpg"); - auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, 0); + auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, STBI_rgb); if (data == nullptr) { throw std::runtime_error("Failed to load image: " + std::string(stbi_failure_reason())); } + channels = STBI_rgb; img = std::vector(data, data + (static_cast(width * height * channels))); stbi_image_free(data); if (std::cmp_not_equal(width, height)) { diff --git a/tasks/example_threads/data/pic.jpg b/tasks/example_threads/data/pic.jpg index 3445802349..637624238c 100644 Binary files a/tasks/example_threads/data/pic.jpg and b/tasks/example_threads/data/pic.jpg differ diff --git a/tasks/example_threads/tests/functional/main.cpp b/tasks/example_threads/tests/functional/main.cpp index b445c09eba..2e99e7bf67 100644 --- a/tasks/example_threads/tests/functional/main.cpp +++ b/tasks/example_threads/tests/functional/main.cpp @@ -35,13 +35,14 @@ class NesterovARunFuncTestsThreads : public ppc::util::BaseRunFuncTests img; - // Read image + // Read image in RGB to ensure consistent channel count { std::string abs_path = ppc::util::GetAbsoluteTaskPath(std::string(PPC_ID_example_threads), "pic.jpg"); - auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, 0); + auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, STBI_rgb); if (data == nullptr) { throw std::runtime_error("Failed to load image: " + std::string(stbi_failure_reason())); } + channels = STBI_rgb; img = std::vector(data, data + (static_cast(width * height * channels))); stbi_image_free(data); if (std::cmp_not_equal(width, height)) { @@ -67,8 +68,7 @@ class NesterovARunFuncTestsThreads : public ppc::util::BaseRunFuncTests