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..83212f0188 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/common/include/common.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#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 = 0; // 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/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..c7f010fa0f --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/mpi/src/ops_mpi.cpp @@ -0,0 +1,138 @@ +#include "dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "dorofeev_i_monte_carlo_integration/common/include/common.hpp" + +namespace dorofeev_i_monte_carlo_integration_processes { + +DorofeevIMonteCarloIntegrationMPI::DorofeevIMonteCarloIntegrationMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool DorofeevIMonteCarloIntegrationMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + bool valid = true; + + if (rank == 0) { + const auto &in = GetInput(); + valid = + in.func && !in.a.empty() && in.a.size() == in.b.size() && + std::ranges::all_of(std::views::iota(size_t{0}, in.a.size()), [&](size_t i) { return in.b[i] > in.a[i]; }) && + in.samples > 0; + } + + MPI_Bcast(&valid, 1, MPI_C_BOOL, 0, MPI_COMM_WORLD); + return valid; +} + +bool DorofeevIMonteCarloIntegrationMPI::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool DorofeevIMonteCarloIntegrationMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + // BROADCAST INPUT + InType in; + + if (rank == 0) { + in = GetInput(); + } + + // BROADCAST DIMS + int dims = static_cast(rank == 0 ? in.a.size() : 0); + MPI_Bcast(&dims, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // resize on other ranks + if (rank != 0) { + in.a.resize(dims); + in.b.resize(dims); + } + + // BROADCAST BOUNDS + MPI_Bcast(in.a.data(), dims, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(in.b.data(), dims, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + // BROADCAST SAMPLES + MPI_Bcast(&in.samples, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // BROADCAST FUNC ID + int func_id = (rank == 0 ? 1 : 0); + MPI_Bcast(&func_id, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // restore the function + switch (func_id) { + case 1: + in.func = [](const std::vector &x) { return x[0] * x[0]; }; + break; + default: + in.func = nullptr; + } + + // CALCULATIONS + int n_total = in.samples; + int n_local = n_total / size; + if (rank == size - 1) { + n_local += n_total % size; + } + + std::vector> dist; + dist.reserve(dims); + for (int dim = 0; std::cmp_less(dim, dims); dim++) { + dist.emplace_back(in.a[dim], in.b[dim]); + } + + std::mt19937 gen(rank + 777); + double local_sum = 0.0; + + std::vector x; + x.assign(static_cast(dims), 0.0); + + for (int i = 0; i < n_local; ++i) { + for (int dim = 0; std::cmp_less(dim, dims); dim++) { + x[static_cast(dim)] = dist[dim](gen); + } + local_sum += in.func(x); + } + + // REDUCE + double global_sum = 0.0; + MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + // RESULT + double result = 0.0; + if (rank == 0) { + double volume = 1.0; + for (int dim = 0; std::cmp_less(dim, dims); dim++) { + volume *= (in.b[dim] - in.a[dim]); + } + 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..76b4b25116 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/report.md @@ -0,0 +1,158 @@ +# Интегрирование – метод Монте-Карло + +**Student:** Дорофеев Иван Денисович, group 3823Б1ФИ1 +**Technology:** SEQ | MPI +**Variant:** 21 + +--- + +## 1. Introduction + +Метод Монте-Карло — один из универсальных способов численного интегрирования, позволяющий эффективно оценивать интегралы любой размерности. Однако точность метода и время работы напрямую зависят от количества случайных выборок (samples). Увеличение количества выборок повышает точность, но также увеличивает время выполнения, что делает задачу подходящей для распараллеливания. + +Ожидается, что MPI-версия позволит уменьшить время выполнения за счёт распределения выборок между процессами. + +--- + +## 2. Problem statement + +Требуется вычислить значение определённого интеграла методом Монте-Карло. + +### Входные данные: + +- Границы интегрирования: + $a = 0,\quad b = 1$ + +- Количество выборок: 190000 + +- Функция: + $f(x) = x^2$ + +### Выходные данные: + +- Одно действительное число — приближённое значение интеграла. + +--- + +## 3. Baseline Algorithm (Sequential) + +1. Сгенерировать N равномерных случайных точек из интервала $[a, b]$. + +2. Вычислить значение функции в каждой точке. + +3. Найти среднее всех вычисленных значений. + +4. Умножить среднее на длину интервала $(b-a)$. + +Последовательная реализация проста, но требует выполнения всех N вычислений в одном процессе. + +--- + +## 4. Parallelization Scheme (MPI) + +Для распараллеливания используется идея независимости выборок: +каждый процесс может самостоятельно выполнить часть выборок и затем отправить свой частичный результат. + +### Схема: + +1. Корневой процесс рассылает параметры задачи всем участникам. + +2. Каждый процесс: + + - выполняет часть выборок: + $N_p = \frac{N}{P} $ + + - рассчитывает среднее значение функции на своём подмножестве. + +3. Результаты всех процессов объединяются с помощью **MPI_Reduce**. + +4. Корневой процесс вычисляет итоговый интеграл. + +Поскольку метод Монте-Карло "естественно" распараллеливается, эффективность MPI в идеальных условиях может быть высокой. + +--- + +## 5. Experimental Setup + +- Hardware/OS: + - CPU: 13th Gen Intel i5-13420H (12) @ 4.6GHz, 8 ядер + - RAM: 16GB RAM + - OS: Ubuntu 25.10 x86_64 + - Среда выполнения: Docker (Ubuntu trixie/sid (noble) x86_64) +- Toolchain: compiler, version, build type (Release/RelWithDebInfo) + - CMake 3.28.3 + - g++ 13.3.0 + - OpenMPI + - Сборка: Release +- Data: + - Количество выборок: 190000 + - Функция: $f(x)=x^2$ + - Тесты производительности запускались для **SEQ**, **MPI: 2**, **MPI: 4** процессов. + +--- + +## 6. Results and Discussion + +### 6.1 Correctness + +Корректность проверена тестами GoogleTest. +Точное значение интеграла: + +$\int_0^1 x^2 dx = \frac{1}{3} \approx 0.333333 $ + + +Все варианты укладываются в допуск ±0.05. + +--- + +## 6.2 Performance + +| Mode | Processes | Time (s) | Speedup | Efficiency | +| ------- | --------- | ------------ | -------- | ---------- | +| **seq** | 1 | **0.002277** | **1.00** | — | +| **mpi** | 2 | **0.001751** | **1.30** | **65%** | +| **mpi** | 4 | **0.001137** | **2.00** | **50%** | + +--- + +## 7. Discussion + +Результаты экспериментов показывают, что MPI-версия действительно работает быстрее последовательной SEQ-реализации при одинаковом количестве выборок. + +Для 2 процессов ускорение составляет **1.30×** при эффективности **65%**. +Для 4 процессов ускорение достигает **2.00×**, что соответствует эффективности **50%**. + +Эффективность снижается с ростом числа процессов, это ожидаемое поведение для MPI, так как накладные расходы растут, а объём работы, приходящийся на каждый процесс, уменьшается. + +Основные источники потерь производительности: + +- необходимость синхронизации между процессами, + +- выполнение коллективных операций (например, **MPI_Allreduce**), + +- накладные расходы MPI, усиливающиеся при запуске внутри Docker-контейнера, + +- относительно малый объём вычислений на один процесс при 4-процессном запуске. + +Тем не менее ускорение остаётся значительным — даже с учётом накладных расходов распараллеливание уменьшает общее время расчёта примерно вдвое. + +--- + +## 8. Conclusions + +Метод Монте-Карло хорошо поддаётся параллелизации, и MPI-версия показывает ощутимое ускорение. + +Хотя эффективность не достигает 100% из-за накладных расходов, ускорение в 2 раза при использовании 4 процессов подтверждает преимущества распараллеливания. + +MPI-подход особенно полезен при значительно больших объёмах выборок, где накладные расходы становятся менее заметными. + +--- + +## 9. References + +1. "Параллельное программирование для кластерных систем" ИИТММ, ННГУ им. Лобачевского +2. [Open MPI Documentation](https://www.open-mpi.org/doc/) +3. [MPI Reference - Message Passing Interface | Microsoft Learn](https://learn.microsoft.com/en-us/message-passing-interface/mpi-reference) +4. [MPI: A Message-Passing Interface Standard](https://www.mpi-forum.org/docs/mpi-5.0/mpi50-report.pdf) + + 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..ed6d461605 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/seq/src/ops_seq.cpp @@ -0,0 +1,69 @@ +#include "dorofeev_i_monte_carlo_integration/seq/include/ops_seq.hpp" + +#include +#include +#include +#include +#include +#include + +#include "dorofeev_i_monte_carlo_integration/common/include/common.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(); + return in.func && !in.a.empty() && in.a.size() == in.b.size() && + std::ranges::all_of(std::views::iota(size_t{0}, in.a.size()), [&](size_t i) { return in.b[i] > in.a[i]; }) && + in.samples > 0; +} + +bool DorofeevIMonteCarloIntegrationSEQ::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool DorofeevIMonteCarloIntegrationSEQ::RunImpl() { + const auto &in = GetInput(); + const std::size_t dims = in.a.size(); + const int n = in.samples; + + int seed = 777; + std::mt19937 gen(seed); + std::vector> dist; + dist.reserve(dims); + + for (std::size_t i = 0; std::cmp_less(i, dims); i++) { + dist.emplace_back(in.a[i], in.b[i]); + } + + double sum = 0.0; + std::vector point(dims); + + for (int sample = 0; sample < n; sample++) { + for (std::size_t dim = 0; dim < dims; dim++) { + point[dim] = dist[dim](gen); + } + sum += in.func(point); + } + + double volume = 1.0; + for (std::size_t i = 0; std::cmp_less(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..e411147cf4 --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/tests/functional/main.cpp @@ -0,0 +1,174 @@ +#include + +#include +#include +#include +#include +#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" + +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_.a = {0.0}; + input_.b = {1.0}; + input_.samples = samples; + input_.func = [](const std::vector &x) { return x[0] * x[0]; }; + } + + bool CheckTestOutputData(OutType &out) final { + return std::abs(out - (1.0 / 3.0)) < 0.05; + } + + InType GetTestInputData() final { + return input_; + } + + private: + InType input_; +}; + +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); + +class MonteCarloExtraFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintName( + const testing::TestParamInfo> &info) { + const auto &full = info.param; + const TestType &t = std::get<2>(full); + std::string task_name = std::get<1>(full); + return task_name + "_extra_" + std::get<1>(t); + } + + protected: + void SetUp() override { + auto full = GetParam(); + TestType t = std::get<2>(full); + int scenario = std::get<0>(t); + + switch (scenario) { + case 0: // tiny interval + input_.a = {0.0}; + input_.b = {1e-6}; + input_.samples = 5000; + break; + + case 1: // shifted interval + input_.a = {2.0}; + input_.b = {3.0}; + input_.samples = 8000; + break; + + case 2: // samples = 1 + input_.a = {0.0}; + input_.b = {1.0}; + input_.samples = 1; + break; + + case 3: // large samples + input_.a = {0.0}; + input_.b = {1.0}; + input_.samples = 100000; + break; + + case 4: // negative interval + input_.a = {-1.0}; + input_.b = {1.0}; + input_.samples = 20000; + break; + default: + // clang-tidy requires default, but we do nothing + break; + } + + input_.func = [](const std::vector &x) { return x[0] * x[0]; }; + } + + bool CheckTestOutputData(OutType &out) final { + auto full = GetParam(); + int scenario = std::get<0>(std::get<2>(full)); + + switch (scenario) { + case 0: // tiny interval [0, 1e-6] + return std::abs(out - (1e-18 / 3.0)) < 1e-10; + + case 1: { // shifted interval [2, 3] + double exact = (std::pow(3.0, 3) - std::pow(2.0, 3)) / 3.0; + return std::abs(out - exact) < 0.2; + } + + case 2: // samples=1, любое значение допустимо, главное в пределах диапазона + return out >= 0.0 && out <= 1.0; + + case 3: // high accuracy expected + return std::abs(out - (1.0 / 3.0)) < 0.01; + + case 4: // integral x^2 on [-1,1] = 2/3 + return std::abs(out - (2.0 / 3.0)) < 0.05; + default: + return false; // unreachable but required by clang-tidy + } + return false; + } + + InType GetTestInputData() final { + return input_; + } + + private: + InType input_; +}; + +TEST_P(MonteCarloExtraFuncTests, CornerCaseTests) { + ExecuteTest(GetParam()); +} + +const std::array kExtraParams = { + std::make_tuple(0, "tiny_interval"), std::make_tuple(1, "shifted_interval"), std::make_tuple(2, "samples_1"), + std::make_tuple(3, "large_samples"), std::make_tuple(4, "negative_interval"), +}; + +const auto kExtraTaskList = std::tuple_cat( + ppc::util::AddFuncTask(kExtraParams, PPC_SETTINGS_example_processes), + ppc::util::AddFuncTask(kExtraParams, PPC_SETTINGS_example_processes)); + +INSTANTIATE_TEST_SUITE_P(ExtraIntegrationTests, MonteCarloExtraFuncTests, ppc::util::ExpandToValues(kExtraTaskList), + MonteCarloExtraFuncTests::PrintName); + +} // 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..04de9089ba --- /dev/null +++ b/tasks/dorofeev_i_monte_carlo_integration/tests/performance/main.cpp @@ -0,0 +1,61 @@ +#include + +#include +#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 kPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_example_processes); + +// converting to GTest values +const auto kGTestValues = ppc::util::TupleToGTestValues(kPerfTasks); + +// test naming function +const auto kTestName = MonteCarloPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(MonteCarloPerf, MonteCarloPerfTests, kGTestValues, kTestName); + +} // namespace +} // namespace dorofeev_i_monte_carlo_integration_processes