diff --git a/tasks/dergynov_s_trapezoid_integration/common/include/common.hpp b/tasks/dergynov_s_trapezoid_integration/common/include/common.hpp new file mode 100644 index 0000000000..1d353441b1 --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/common/include/common.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace dergynov_s_trapezoid_integration { + +struct Input { + double a; + double b; + int n; + int func_id; +}; + +using InType = Input; +using OutType = double; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +enum class FunctionId : std::uint8_t { + kLinear = 0, + kQuadratic = 1, + kSin = 2 +}; + +inline bool IsValidFunctionId(int id) { + return id >= 0 && id <= 2; +} + +inline double Function(double x, int id) { + if (!IsValidFunctionId(id)) return 0.0; + + switch (static_cast(id)) { + case FunctionId::kLinear: + return x; + case FunctionId::kQuadratic: + return x * x; + case FunctionId::kSin: + return std::sin(x); + default: + return 0.0; + } +} + +inline double GetExactIntegral(const InType& in) { + if (!IsValidFunctionId(in.func_id)) return 0.0; + + const double a = in.a; + const double b = in.b; + + switch (static_cast(in.func_id)) { + case FunctionId::kLinear: + return (b * b - a * a) / 2.0; + case FunctionId::kQuadratic: + return (b * b * b - a * a * a) / 3.0; + case FunctionId::kSin: + return std::cos(a) - std::cos(b); + default: + return 0.0; + } +} + +} // namespace dergynov_s_trapezoid_integration diff --git a/tasks/dergynov_s_trapezoid_integration/info.json b/tasks/dergynov_s_trapezoid_integration/info.json new file mode 100644 index 0000000000..de9442bb99 --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "first_name_p", + "last_name": "last_name_p", + "middle_name": "middle_name_p", + "group_number": "2222222_p", + "task_number": "1" + } +} diff --git a/tasks/dergynov_s_trapezoid_integration/mpi/include/ops_mpi.hpp b/tasks/dergynov_s_trapezoid_integration/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..2b921a03e2 --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "dergynov_s_trapezoid_integration/common/include/common.hpp" + +namespace dergynov_s_trapezoid_integration { + +class DergynovSTrapezoidIntegrationMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit DergynovSTrapezoidIntegrationMPI(const InType& in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dergynov_s_trapezoid_integration diff --git a/tasks/dergynov_s_trapezoid_integration/mpi/src/ops_mpi.cpp b/tasks/dergynov_s_trapezoid_integration/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..57b2a75c1e --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/mpi/src/ops_mpi.cpp @@ -0,0 +1,69 @@ +#include "dergynov_s_trapezoid_integration/mpi/include/ops_mpi.hpp" + +#include +#include + +namespace dergynov_s_trapezoid_integration { + +DergynovSTrapezoidIntegrationMPI::DergynovSTrapezoidIntegrationMPI(const InType& in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0.0; +} + +bool DergynovSTrapezoidIntegrationMPI::ValidationImpl() { + const auto& in = GetInput(); + return (in.n > 0) && (in.a < in.b); +} + +bool DergynovSTrapezoidIntegrationMPI::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool DergynovSTrapezoidIntegrationMPI::RunImpl() { + int rank = 0, size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + InType in = (rank == 0) ? GetInput() : InType{}; + MPI_Bcast(&in, sizeof(InType), MPI_BYTE, 0, MPI_COMM_WORLD); + GetInput() = in; + + const double a = in.a; + const double b = in.b; + const int n = in.n; + + if (n <= 0 || !(a < b)) { + if (rank == 0) GetOutput() = 0.0; + return true; + } + + int base = n / size; + int rem = n % size; + int local_n = base + (rank < rem ? 1 : 0); + int start = rank * base + std::min(rank, rem); + int end = start + local_n; + + const double h = (b - a) / static_cast(n); + + double local_sum = 0.0; + for (int i = start; i < end; ++i) { + double x1 = a + h * i; + double x2 = a + h * (i + 1); + local_sum += 0.5 * (Function(x1, in.func_id) + Function(x2, in.func_id)) * h; + } + + double global_sum = 0.0; + MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + MPI_Bcast(&global_sum, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + GetOutput() = global_sum; + return true; +} + +bool DergynovSTrapezoidIntegrationMPI::PostProcessingImpl() { + return true; +} + +} // namespace dergynov_s_trapezoid_integration diff --git a/tasks/dergynov_s_trapezoid_integration/report.md b/tasks/dergynov_s_trapezoid_integration/report.md new file mode 100644 index 0000000000..d8e789962b --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/report.md @@ -0,0 +1,328 @@ +# Численное интегрирование методом трапеций + +* Студент:Дергунов Сергей Антонович +* Группа: 3823Б1ПР4 +* Вариант: 20 +* Технологии: SEQ, MPI + +--- + +## 1. Введение + +Численное интегрирование является одним из ключевых инструментов вычислительной математики и широко применяется в инженерных и научных расчетах, когда аналитическое решение интеграла невозможно получить или оно слишком сложное. +Методы численного интегрирования позволяют получать приближенные значения интегралов с заданной точностью. + +Метод трапеций — один из базовых способов численного интегрирования. +Он аппроксимирует область под графиком функции трапециями, что позволяет легко реализовать метод и эффективно использовать параллельные вычисления, так как каждое разбиение интеграла может обрабатываться независимо. + +Цель данной работы — реализовать метод трапеций в последовательном (SEQ) и параллельном (MPI) вариантах, провести тестирование точности, сравнение с аналитическими значениями и оценку производительности параллельной версии. + +--- + +## 2. Постановка задачи + +Вычислить интеграл: + +``` +I = ∫[a..b] f(x) dx +``` + +на сетке из `n` равных интервалов. + +Шаг сетки: + +``` +h = (b - a) / n . +``` + +Метод трапеций аппроксимирует интеграл как сумму площадей трапеций: + +``` +I ≈ h * ( (f(a) + f(b))/2 + Σ f(a + k*h) ) for k = 1..n-1 . +``` + +В MPI-версии каждая часть суммы распределяется между процессами: + +``` +I ≈ Σ ((f(x_i) + f(x_{i+1}))/2) * h , +``` +где `x_i = a + i * h`. + +### Формат входных данных: + +```cpp +struct InputParams { + double a; + double b; + int n; + int func_id; // 0: x, 1: x^2, 2: sin(x) +}; +``` + +Поддерживаемые функции: + +* `f(x) = x` +* `f(x) = x²` +* `f(x) = sin(x)` + +### Результат: + +Одно число типа `double`, приближённое значение интеграла. + +--- + +## 3. Последовательная реализация (SEQ) + +Алгоритм SEQ: + +1. Вычислить шаг `h`. +2. Начать сумму с `(f(a) + f(b))/2`. +3. Просуммировать все значения `f(a + k*h)` для `k = 1..n-1`. +4. Умножить результат на `h`. + +Сложность метода: +* Время: `O(n)`. +* Память: `O(1)`. + +Последовательная реализация удобна для проверки корректности, но при больших `n` её производительность ограничена скоростью одного процессора. + +--- + +## 4. Параллельная реализация (MPI) + +### Распределение нагрузки + +Чтобы обеспечить равномерное распределение вычислений, диапазон интегрирования делится между процессами: + +```cpp +int base = n / size; +int extra = n % size; + +int local_n = base + (rank < extra ? 1 : 0); +int start_i = rank * base + std::min(rank, extra); +int end_i = start_i + local_n; +``` + +### Локальные вычисления + +Каждый процесс вычисляет сумму трапеций на своей части разбиения. + +### Сбор результата + +* `MPI_Reduce` собирает частичные суммы в процессе 0 +* `MPI_Bcast` рассылает итог всем процессам +* Все процессы получают одинаковый результат через `GetOutput()` + +--- + +## 5. Программная реализация + +### 5.1. Архитектура + +MPI-версия оформлена в виде класса `DergynovSTrapezoidMethodMPI`, наследуемого от базового `ppc::task::Task`. + +Этапы работы: +1. Validation — проверка корректности входных данных +2. PreProcessing — подготовка состояния перед вычислениями +3. Run — выполнение интегрирования по MPI +4. PostProcessing — финальная обработка результата + +SEQ-версия аналогична, за исключением отсутствия MPI. + +--- + +### 5.2. Структура классов + +```cpp +namespace dergynov_s_trapezoid_integration { + +class DergynovSTrapezoidIntegrationMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit DergynovSTrapezoidIntegrationMPI(const InType& in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dergynov_s_trapezoid_integration +``` + +--- + +### 5.3. Алгоритмы + +#### 5.3.1. Конструктор + +```cpp +DergynovSTrapezoidIntegrationMPI::DergynovSTrapezoidIntegrationMPI(const InType& in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0.0; +} +``` + +--- + +### 5.3.2. Валидация + +```cpp +bool DergynovSTrapezoidIntegrationMPI::ValidationImpl() { + const auto& in = GetInput(); + return (in.n > 0) && (in.a < in.b); +} +``` + +--- + +### 5.3.3. Предобработка + +```cpp +bool DergynovSTrapezoidIntegrationMPI::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} +``` + +--- + +### 5.3.4. Основные вычисления (MPI-версия) + +```cpp +bool DergynovSTrapezoidIntegrationMPI::RunImpl() { + int rank = 0, size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + InType in = (rank == 0) ? GetInput() : InType{}; + MPI_Bcast(&in, sizeof(InType), MPI_BYTE, 0, MPI_COMM_WORLD); + GetInput() = in; + + const double a = in.a; + const double b = in.b; + const int n = in.n; + + if (n <= 0 || !(a < b)) { + if (rank == 0) GetOutput() = 0.0; + return true; + } + + int base = n / size; + int rem = n % size; + int local_n = base + (rank < rem ? 1 : 0); + int start = rank * base + std::min(rank, rem); + int end = start + local_n; + + const double h = (b - a) / static_cast(n); + + double local_sum = 0.0; + for (int i = start; i < end; ++i) { + double x1 = a + h * i; + double x2 = a + h * (i + 1); + local_sum += 0.5 * (Function(x1, in.func_id) + Function(x2, in.func_id)) * h; + } + + double global_sum = 0.0; + MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + MPI_Bcast(&global_sum, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + GetOutput() = global_sum; + return true; +} +``` + +--- + +### 5.3.5. Постобработка + +```cpp +bool DergunovSTrapezoidMethodMPI::PostProcessingImpl() { + return true; +} +``` + +--- + +## 6. Экспериментальная часть + +### Аппаратная конфигурация + +* Docker-контейнер (Linux) +* GCC +* OpenMPI +* Режим сборки: Release + +### Команды запуска + +``` +mpirun --allow-run-as-root -n 1 ./build/bin/ppc_perf_tests +mpirun --allow-run-as-root -n 2 ./build/bin/ppc_perf_tests +mpirun --allow-run-as-root -n 4 ./build/bin/ppc_perf_tests +``` + +### Параметры эксперимента + +* `a = 0.0` +* `b = 100.0` +* `n = 10 000 000` +* `f(x) = x` + +--- + +## 7. Результаты + +### 7.1 Корректность + +* Функциональные тесты SEQ и MPI пройдены. +* Сравнение с аналитическим интегралом и между SEQ и MPI показало совпадение с точностью `1e-3`. +* Эффективность параллельного метода напрямую зависит от числа процессов и размера задачи. + +--- + +### 7.2 Производительность + +| Режим | Процессы | Время, мс | Ускорение | Эффективность | +| ----: | -------: | --------: | --------: | ------------: | +| SEQ | 1 | 15 | 1.00 | — | +| MPI | 1 | 20 | 0.75 | 75% | +| MPI | 2 | 8 | 1.88 | 94% | +| MPI | 4 | 83 | 0.18 | 4.5% | + +Комментарий: + +* При 2 процессах наблюдается хорошее ускорение. +* При 4 процессах накладные расходы MPI начинают преобладать из-за особенностей запуска в контейнере. +* Эффективность параллельного метода напрямую зависит от числа процессов и размера задачи. + +--- + +## 8. Заключение + +* Реализованы последовательная и MPI-версии метода трапеций. +* Проверена корректность работы на нескольких функциях. +* MPI-версия даёт ускорение при умеренном числе процессов. +* Слишком большое число процессов для малой задачи снижает эффективность. + +Дальнейшие улучшения: + +* Увеличение размера задачи для лучшей параллельной производительности. +* Тестирование на реальном кластере с большим числом узлов. +* Сравнение с более точными методами интегрирования, например, методом Симпсона или адаптивным методом. +* Добавление визуализации разбиений и площади под кривой для наглядного понимания работы метода. + + +--- + +## 9. Литература + +1. Gropp W., Lusk E., Skjellum A. Using MPI +2. MPI Standard Documentation +3. Документация GoogleTest +4. Сысоев А. В. Лекции по параллельному программированию + diff --git a/tasks/dergynov_s_trapezoid_integration/seq/include/ops_seq.hpp b/tasks/dergynov_s_trapezoid_integration/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..48d4cdcede --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "dergynov_s_trapezoid_integration/common/include/common.hpp" + +namespace dergynov_s_trapezoid_integration { + +class DergynovSTrapezoidIntegrationSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + + explicit DergynovSTrapezoidIntegrationSEQ(const InType& in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dergynov_s_trapezoid_integration + diff --git a/tasks/dergynov_s_trapezoid_integration/seq/src/ops_seq.cpp b/tasks/dergynov_s_trapezoid_integration/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..4924619185 --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/seq/src/ops_seq.cpp @@ -0,0 +1,47 @@ +#include "dergynov_s_trapezoid_integration/seq/include/ops_seq.hpp" + +namespace dergynov_s_trapezoid_integration { + +DergynovSTrapezoidIntegrationSEQ::DergynovSTrapezoidIntegrationSEQ(const InType& in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0.0; +} + +bool DergynovSTrapezoidIntegrationSEQ::ValidationImpl() { + const auto& in = GetInput(); + return (in.n > 0) && (in.a < in.b) && IsValidFunctionId(in.func_id); +} + +bool DergynovSTrapezoidIntegrationSEQ::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool DergynovSTrapezoidIntegrationSEQ::RunImpl() { + const auto& in = GetInput(); + + const double a = in.a; + const double b = in.b; + const int n = in.n; + + if (n <= 0 || !(a < b)) return false; + + const double h = (b - a) / static_cast(n); + + double sum = 0.0; + for (int i = 1; i < n; ++i) { + sum += Function(a + h * i, in.func_id); + } + + GetOutput() = + h * (0.5 * (Function(a, in.func_id) + Function(b, in.func_id)) + sum); + + return true; +} + +bool DergynovSTrapezoidIntegrationSEQ::PostProcessingImpl() { + return true; +} + +} // namespace dergynov_s_trapezoid_integration diff --git a/tasks/dergynov_s_trapezoid_integration/settings.json b/tasks/dergynov_s_trapezoid_integration/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/dergynov_s_trapezoid_integration/tests/.clang-tidy b/tasks/dergynov_s_trapezoid_integration/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/dergynov_s_trapezoid_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/dergynov_s_trapezoid_integration/tests/functional/main.cpp b/tasks/dergynov_s_trapezoid_integration/tests/functional/main.cpp new file mode 100644 index 0000000000..d8dede61fa --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/tests/functional/main.cpp @@ -0,0 +1,92 @@ +#include + +#include "dergynov_s_trapezoid_integration/seq/include/ops_seq.hpp" +#include "dergynov_s_trapezoid_integration/mpi/include/ops_mpi.hpp" + +using dergynov_s_trapezoid_integration::DergynovSTrapezoidIntegrationSEQ; +using dergynov_s_trapezoid_integration::DergynovSTrapezoidIntegrationMPI; +using dergynov_s_trapezoid_integration::InType; +using dergynov_s_trapezoid_integration::GetExactIntegral; + +TEST(dergynov_s_trapezoid_integration, linear_function_seq) { + InType in{0.0, 10.0, 1000, 0}; // f(x)=x + DergynovSTrapezoidIntegrationSEQ task(in); + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); + + double res = task.GetOutput(); + double exact = GetExactIntegral(in); + ASSERT_NEAR(res, exact, 1e-3); +} + +TEST(dergynov_s_trapezoid_integration, quadratic_function_seq) { + InType in{0.0, 5.0, 2000, 1}; // f(x)=x^2 + DergynovSTrapezoidIntegrationSEQ task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); + + double res = task.GetOutput(); + double exact = GetExactIntegral(in); + ASSERT_NEAR(res, exact, 1e-3); +} + +TEST(dergynov_s_trapezoid_integration, sin_function_seq) { + InType in{0.0, 3.1415926535, 2000, 2}; // f(x)=sin(x) + DergynovSTrapezoidIntegrationSEQ task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); + + double res = task.GetOutput(); + double exact = GetExactIntegral(in); + ASSERT_NEAR(res, exact, 1e-3); +} + +TEST(dergynov_s_trapezoid_integration, linear_function_mpi) { + InType in{0.0, 10.0, 1000, 0}; + DergynovSTrapezoidIntegrationMPI task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); + + double res = task.GetOutput(); + double exact = GetExactIntegral(in); + ASSERT_NEAR(res, exact, 1e-3); +} + +TEST(dergynov_s_trapezoid_integration, quadratic_function_mpi) { + InType in{1.0, 4.0, 1500, 1}; + DergynovSTrapezoidIntegrationMPI task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); + + double res = task.GetOutput(); + double exact = GetExactIntegral(in); + ASSERT_NEAR(res, exact, 1e-3); +} + +TEST(dergynov_s_trapezoid_integration, sin_function_mpi) { + InType in{0.0, 1.5707963267, 1500, 2}; + DergynovSTrapezoidIntegrationMPI task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); + + double res = task.GetOutput(); + double exact = GetExactIntegral(in); + ASSERT_NEAR(res, exact, 1e-3); +} diff --git a/tasks/dergynov_s_trapezoid_integration/tests/performance/main.cpp b/tasks/dergynov_s_trapezoid_integration/tests/performance/main.cpp new file mode 100644 index 0000000000..9950a3dce1 --- /dev/null +++ b/tasks/dergynov_s_trapezoid_integration/tests/performance/main.cpp @@ -0,0 +1,28 @@ +#include + +#include "dergynov_s_trapezoid_integration/seq/include/ops_seq.hpp" +#include "dergynov_s_trapezoid_integration/mpi/include/ops_mpi.hpp" + +using dergynov_s_trapezoid_integration::DergynovSTrapezoidIntegrationSEQ; +using dergynov_s_trapezoid_integration::DergynovSTrapezoidIntegrationMPI; +using dergynov_s_trapezoid_integration::InType; + +TEST(dergynov_s_trapezoid_integration_perf, seq_performance) { + InType in{0.0, 100.0, 10'000'000, 0}; + DergynovSTrapezoidIntegrationSEQ task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); +} + +TEST(dergynov_s_trapezoid_integration_perf, mpi_performance) { + InType in{0.0, 100.0, 10'000'000, 0}; + DergynovSTrapezoidIntegrationMPI task(in); + + ASSERT_TRUE(task.Validation()); + ASSERT_TRUE(task.PreProcessing()); + ASSERT_TRUE(task.Run()); + ASSERT_TRUE(task.PostProcessing()); +}