-
Notifications
You must be signed in to change notification settings - Fork 80
Кутергин Валентин. Технология SEQ-MPI. Интегрирование - метод трапеций. Вариант 20. #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
allnes
merged 22 commits into
learning-process:master
from
VALancaster:kutergin_v_trapezoid_method_of_integration
Dec 10, 2025
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
d1931e1
feat: Add initial sequential implementation for trapezoid method
VALancaster e1a00c6
feat: Add MPI implementation and refactor file structure
VALancaster 4c64275
So... let's try CI pipeline
VALancaster 34ee0a4
style: Apply clang-format
VALancaster 0d947a7
fix: Make test 'n' divisible by process count
VALancaster c6f9513
style: Apply some clang-format fixes
VALancaster 62ea508
style: Apply some clang-format fixes 2.0
VALancaster f94b474
fix: Guard all MPI calls with IsUnderMpirun check
VALancaster 3271837
refactor: Implement robust handling of non-divisible 'n' in MPI
VALancaster e29f81b
fix: Address clang-tidy warnings
VALancaster cca8cca
fix: Address clang-tidy warnings 2.0
VALancaster 71dc973
fix: Address clang-tidy warnings 3.0
VALancaster 658f1d5
fix: Address clang-tidy warnings 4.0
VALancaster ece5816
feat: Finalize Task 1 with full functional test coverage
VALancaster 0b4fb74
feat: Add performance tests and apply format/tidy fixes
VALancaster 9d5547a
perf: Increase problem size to meet CI time threshold
VALancaster ff29fc3
docs: Add report.md
VALancaster fe86e42
test: Add final tests to achieve >95% code coverage
VALancaster 40f977e
fix: Address all review comments and finalize tests
VALancaster da85fd1
refactor: Move MPI communication from PreProcessing to RunImpl
VALancaster 40c25ac
fix: Revert changes to common files per review
VALancaster c667f9c
chore: Revert devcontainer.json to upstream version
VALancaster File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
22 changes: 22 additions & 0 deletions
22
tasks/kutergin_v_trapezoid_method_of_integration/common/include/common.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| #pragma once | ||
|
|
||
| #include <string> | ||
| #include <tuple> | ||
|
|
||
| #include "task/include/task.hpp" | ||
|
|
||
| namespace kutergin_v_trapezoid_seq { | ||
|
|
||
| struct InputData { | ||
| double a; | ||
| double b; | ||
| int n; | ||
| }; | ||
|
|
||
| using InType = InputData; | ||
| using OutType = double; | ||
| using BaseTask = ppc::task::Task<InType, OutType>; | ||
| using TestType = std::tuple<InputData, double, std::string>; // Тип для тестовых параметров {входные данные, ожидаемый | ||
| // результат, имя_теста} | ||
|
|
||
| } // namespace kutergin_v_trapezoid_seq | ||
1 change: 1 addition & 0 deletions
1
tasks/kutergin_v_trapezoid_method_of_integration/data/input.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 0.0 10.0 3000000 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "student": { | ||
| "first_name": "Валентин", | ||
| "last_name": "Кутергин", | ||
| "middle_name": "Тимофеевич", | ||
| "group_number": "3823Б1ФИ3 ", | ||
| "task_number": "1" | ||
| } | ||
| } |
27 changes: 27 additions & 0 deletions
27
tasks/kutergin_v_trapezoid_method_of_integration/mpi/include/trapezoid_integration_mpi.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #pragma once | ||
|
|
||
| #include "../../common/include/common.hpp" | ||
| #include "task/include/task.hpp" | ||
|
|
||
| namespace kutergin_v_trapezoid_mpi { | ||
|
|
||
| double Func(double x); | ||
|
|
||
| class TrapezoidIntegrationMPI | ||
| : public kutergin_v_trapezoid_seq::BaseTask // наследник BaseTask (псевдоним для ppc::task::Task<InType, OutType>) | ||
| { | ||
| public: | ||
| static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { | ||
| return ppc::task::TypeOfTask::kMPI; | ||
| } | ||
| explicit TrapezoidIntegrationMPI( | ||
| const kutergin_v_trapezoid_seq::InType &in); // конструктор принимает InType (псевдоним для структуры InputData) | ||
|
|
||
| protected: | ||
| bool ValidationImpl() override; | ||
| bool PreProcessingImpl() override; | ||
| bool RunImpl() override; | ||
| bool PostProcessingImpl() override; | ||
| }; | ||
|
|
||
| } // namespace kutergin_v_trapezoid_mpi |
90 changes: 90 additions & 0 deletions
90
tasks/kutergin_v_trapezoid_method_of_integration/mpi/src/trapezoid_integration_mpi.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| #include "../include/trapezoid_integration_mpi.hpp" | ||
|
|
||
| #include <mpi.h> | ||
|
|
||
| #include <cmath> | ||
|
|
||
| #include "../../common/include/common.hpp" | ||
|
|
||
| namespace kutergin_v_trapezoid_mpi { | ||
|
|
||
| double Func(double x) // интегрируемая функция для примера | ||
| { | ||
| return x * x; | ||
| } | ||
|
|
||
| TrapezoidIntegrationMPI::TrapezoidIntegrationMPI(const kutergin_v_trapezoid_seq::InType &in) { | ||
| SetTypeOfTask(GetStaticTypeOfTask()); // установка типа задачи | ||
| GetInput() = in; // сохранение входных данных | ||
| GetOutput() = 0.0; // инициализация выходных данных | ||
|
allnes marked this conversation as resolved.
|
||
| } | ||
|
|
||
| bool TrapezoidIntegrationMPI::ValidationImpl() { | ||
| return (GetInput().a <= GetInput().b) && (GetInput().n > 0); | ||
| } | ||
|
|
||
|
allnes marked this conversation as resolved.
|
||
| bool TrapezoidIntegrationMPI::PreProcessingImpl() { | ||
|
allnes marked this conversation as resolved.
|
||
| return true; | ||
| } | ||
|
|
||
| bool TrapezoidIntegrationMPI::RunImpl() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sending the data to different ranks from rank 0 is missing |
||
| int process_rank = 0; | ||
| int process_count = 0; | ||
| MPI_Comm_rank(MPI_COMM_WORLD, &process_rank); | ||
| MPI_Comm_size(MPI_COMM_WORLD, &process_count); | ||
|
|
||
| kutergin_v_trapezoid_seq::InType broadcast_data; | ||
| if (process_rank == 0) { | ||
| broadcast_data = GetInput(); | ||
| } | ||
|
|
||
| // Каждое поле рассылается отдельно | ||
| MPI_Bcast(&broadcast_data.a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); | ||
| MPI_Bcast(&broadcast_data.b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); | ||
| MPI_Bcast(&broadcast_data.n, 1, MPI_INT, 0, MPI_COMM_WORLD); | ||
|
|
||
| double a = broadcast_data.a; | ||
| double b = broadcast_data.b; | ||
| int n = broadcast_data.n; | ||
| double h = (b - a) / n; | ||
|
|
||
| const int base_n = n / process_count; // целое часть от деления числа разбиений на число процессов | ||
| const int remainder = n % process_count; // остаток от деления числа разбиений на число процессов | ||
|
|
||
| const int local_n = base_n + (process_rank < remainder ? 1 : 0); // количество разбиений (трапеций) на один процесс | ||
|
|
||
| int start_index = 0; | ||
| if (process_rank < remainder) { | ||
| start_index = process_rank * (base_n + 1); | ||
| } else { | ||
| start_index = (remainder * (base_n + 1)) + ((process_rank - remainder) * base_n); | ||
| } | ||
|
|
||
| double local_a = a + (start_index * h); // начало отрезка для текущего процесса | ||
|
|
||
| // локальные вычисления | ||
| double local_sum = 0.0; | ||
| if (local_n > 0) { | ||
| local_sum = (Func(local_a) + Func(local_a + (local_n * h))) / 2.0; | ||
| } | ||
|
|
||
| for (int i = 1; i < local_n; ++i) { | ||
| local_sum += Func(local_a + (i * h)); | ||
| } | ||
|
|
||
| // агрегация | ||
| double global_sum = 0.0; | ||
| MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); | ||
|
|
||
| if (process_rank == 0) { | ||
| GetOutput() = global_sum * h; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| bool TrapezoidIntegrationMPI::PostProcessingImpl() { | ||
| return true; | ||
| } | ||
|
|
||
| } // namespace kutergin_v_trapezoid_mpi | ||
125 changes: 125 additions & 0 deletions
125
tasks/kutergin_v_trapezoid_method_of_integration/report.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # Отчет по лабораторной работе №1 | ||
| ## "Интегрирование методом трапеций" | ||
|
|
||
| **Студент:** Кутергин Валентин | ||
| **Группа:** 3823Б1ФИ3 | ||
| **Вариант:** 20 | ||
|
|
||
| ### 1. Введение | ||
| **Мотивация:** Численное интегрирование — фундаментальная задача во многих научных и инженерных областях. Цель данной работы — реализовать параллельную версию алгоритма интегрирования методом трапеций с использованием технологии MPI и исследовать эффективность распараллеливания по сравнению с последовательным вариантом. | ||
|
|
||
| **Проблема:** Метод трапеций, как и многие численные методы, хорошо поддается декомпозиции. Однако, каждая отдельная итерация (вычисление площади одной трапеции) имеет очень низкую вычислительную сложность. Это создает риск, что накладные расходы на организацию параллельного взаимодействия (коммуникации) могут превысить выгоду от параллельных вычислений. | ||
|
|
||
| **Ожидаемый результат:** Ожидается, что MPI-версия покажет ускорение только на задачах с очень большим количеством итераций (`n`), где время вычислений станет сопоставимым с затратами на коммуникацию. Для задач с низкой или средней вычислительной нагрузкой параллельная версия, скорее всего, будет уступать последовательной. | ||
|
|
||
| ### 2. Постановка задачи | ||
| **Задано:** Функция `f(x)`, отрезок интегрирования `[a, b]` и количество шагов разбиения `n`. | ||
|
|
||
| **Требуется:** Численно вычислить определенный интеграл функции `f(x)` на заданном отрезке. | ||
|
|
||
| **Входные данные:** `double a`, `double b`, `int n`. | ||
| **Выходные данные:** `double result` — приближенное значение интеграла. | ||
|
|
||
| ### 3. Базовый алгоритм (Последовательный) | ||
| Последовательный алгоритм реализует формулу метода трапеций: | ||
| `Интеграл ≈ h * [ (f(a) + f(b))/2 + f(x_1) + f(x_2) + ... + f(x_{n-1}) ]`, где `h = (b-a)/n`. | ||
|
|
||
| Алгоритм состоит из одного цикла, который итеративно вычисляет и суммирует значения функции в точках разбиения. Сложность алгоритма — `O(n)`. | ||
|
|
||
| ### 4. Схема распараллеливания | ||
| **Декомпозиция данных:** | ||
| Основная вычислительная нагрузка — это суммирование `n` значений функции. Эта сумма была разделена между `P` MPI-процессами. Используется стратегия **блочного распределения**: общее число итераций `n` делится на `P`. | ||
|
|
||
| **Обработка остатка:** | ||
| Чтобы алгоритм работал для любых `n` и `P`, реализована корректная обработка остатка от деления. Первые `n % P` процессов получают на одну итерацию больше, чем остальные. Это обеспечивает максимально равномерное распределение нагрузки. | ||
|
|
||
| **Вычисления на процессе:** | ||
| Каждый процесс независимо вычисляет свою **локальную сумму** (`local_sum`) на своем уникальном подотрезке. | ||
|
|
||
| **Коммуникация:** | ||
| * В начале работы (этап `PreProcessing`) корневой процесс (`rank 0`) рассылает всем остальным исходные данные (`a`, `b`, `n`) с помощью коллективной операции **`MPI_Bcast`**. | ||
| * После завершения локальных вычислений все процессы участвуют в коллективной операции **`MPI_Reduce`** с операцией `MPI_SUM`. Все `local_sum` суммируются, и итоговый результат (`global_sum`) становится доступен только на корневом процессе. | ||
|
|
||
| ### 5. Экспериментальная установка | ||
| * **Hardware/OS:** AMD Ryzen 5 7500F, 6 ядер, 32GB RAM, Windows 10 | ||
| * **Toolchain & Environment:** | ||
| * Работа производилась внутри **Dev Container** (Ubuntu) | ||
| * Компилятор: **GCC 14.2.0** | ||
| * MPI-библиотека: **Open MPI 3.1** | ||
| * Тип сборки: **Release** | ||
| * **Data:** Для замеров производительности использовалась функция `f(x) = x*x` на отрезке `[0.0, 10.0]` с `n = 25,200,000`. | ||
|
|
||
| ### 6. Результаты и обсуждение | ||
| #### 6.1 Корректность | ||
| Корректность обеих реализаций (`seq` и `mpi`) проверена набором функциональных тестов, включая: | ||
| * Тесты с фиксированными эталонными значениями. | ||
| * Тесты со случайно сгенерированными границами интегрирования и числом шагов `n`. | ||
| Все тесты успешно пройдены, что подтверждает корректность вычислений. | ||
|
|
||
| #### 6.2 Производительность | ||
| Замеры производительности проводились на системе, указанной в п.5. В таблице представлено время выполнения только вычислительной части (`RunImpl`). | ||
|
|
||
| | Mode | Count | Time, s | Speedup | Efficiency | | ||
| | :--- | :--- | :--- | :--- | :--- | | ||
| | seq | 1 | 0.0000289 | 1.00 | N/A | | ||
| | mpi | 4 | 0.0002408 | **0.12** | **3.0%** | | ||
|
|
||
| * **Speedup** = `Time(seq) / Time(mpi)` | ||
| * **Efficiency** = `Speedup / Count` | ||
|
|
||
| Как видно из результатов, параллельная реализация на 4-х процессах оказалась значительно (приблизительно в 8.3 раза) **медленнее** последовательной. Ускорение составило всего 0.12, что говорит о сильной деградации производительности. | ||
|
|
||
| ### 7. Выводы | ||
| Реализация параллельного алгоритма интегрирования методом трапеций с использованием MPI продемонстрировала свою **неэффективность для данной задачи**. | ||
|
|
||
| Основная причина — **крайне низкая вычислительная интенсивность** операции. Время, затрачиваемое на вычисление `x*x`, ничтожно мало по сравнению с **накладными расходами на MPI-коммуникации**. Операции `MPI_Bcast` и `MPI_Reduce` требуют упаковки данных, пересылки и синхронизации, что в данном случае "съедает" весь потенциальный выигрыш от параллельных вычислений и приводит к существенному замедлению. | ||
|
|
||
| ДЭффективность MPI достигается только тогда, когда время, затраченное на локальные вычисления, **значительно превышает** время на коммуникацию между процессами. | ||
|
|
||
| ### 8. Приложение: Исходный код MPI-реализации (`RunImpl`) | ||
| ```cpp | ||
| bool TrapezoidIntegrationMPI::RunImpl() { | ||
| int process_rank = 0; | ||
| int process_count = 0; | ||
| MPI_Comm_rank(MPI_COMM_WORLD, &process_rank); | ||
| MPI_Comm_size(MPI_COMM_WORLD, &process_count); | ||
|
|
||
| double a = GetInput().a; | ||
| double b = GetInput().b; | ||
| int n = GetInput().n; | ||
| double h = (b - a) / n; | ||
|
|
||
| const int base_n = n / process_count; // целое часть от деления числа разбиений на число процессов | ||
| const int remainder = n % process_count; // остаток от деления числа разбиений на число процессов | ||
|
|
||
| const int local_n = base_n + (process_rank < remainder ? 1 : 0); // количество разбиений (трапеций) на один процесс | ||
|
|
||
| int start_index = 0; | ||
| if (process_rank < remainder) { | ||
| start_index = process_rank * (base_n + 1); | ||
| } else { | ||
| start_index = (remainder * (base_n + 1)) + ((process_rank - remainder) * base_n); | ||
| } | ||
|
|
||
| double local_a = a + (start_index * h); // начало отрезка для текущего процесса | ||
|
|
||
| // локальные вычисления | ||
| double local_sum = 0.0; | ||
| if (local_n > 0) { | ||
| local_sum = (Func(local_a) + Func(local_a + (local_n * h))) / 2.0; | ||
| } | ||
|
|
||
| for (int i = 1; i < local_n; ++i) { | ||
| local_sum += Func(local_a + (i * h)); | ||
| } | ||
|
|
||
| // агрегация | ||
| double global_sum = 0.0; | ||
| MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); | ||
|
|
||
| if (process_rank == 0) { | ||
| GetOutput() = global_sum * h; | ||
| } | ||
|
|
||
| return true; | ||
| } |
27 changes: 27 additions & 0 deletions
27
...tergin_v_trapezoid_method_of_integration/seq/include/trapezoid_integration_sequential.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #pragma once | ||
|
|
||
| #include "../../common/include/common.hpp" | ||
| #include "task/include/task.hpp" | ||
|
|
||
| namespace kutergin_v_trapezoid_seq { | ||
|
|
||
| double Func(double x); | ||
|
|
||
| class TrapezoidIntegrationSequential | ||
| : public BaseTask // наследник BaseTask (псевдоним для ppc::task::Task<InType, OutType>) | ||
| { | ||
| public: | ||
| static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { | ||
| return ppc::task::TypeOfTask::kSEQ; | ||
| } | ||
| explicit TrapezoidIntegrationSequential( | ||
| const InType &in); // конструктор принимает InType (псевдоним для структуры InputData) | ||
|
|
||
| protected: | ||
| bool ValidationImpl() override; | ||
| bool PreProcessingImpl() override; | ||
| bool RunImpl() override; | ||
| bool PostProcessingImpl() override; | ||
| }; | ||
|
|
||
| } // namespace kutergin_v_trapezoid_seq |
48 changes: 48 additions & 0 deletions
48
...s/kutergin_v_trapezoid_method_of_integration/seq/src/trapezoid_integration_sequential.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| #include "../include/trapezoid_integration_sequential.hpp" | ||
|
|
||
| #include "../../common/include/common.hpp" | ||
|
|
||
| namespace kutergin_v_trapezoid_seq { | ||
|
|
||
| double Func(double x) // интегрируемая функция для примера | ||
| { | ||
| return x * x; | ||
| } | ||
|
|
||
| TrapezoidIntegrationSequential::TrapezoidIntegrationSequential(const InType &in) { | ||
| SetTypeOfTask(GetStaticTypeOfTask()); // установка типа задачи | ||
| GetInput() = in; // сохранение входных данных | ||
| GetOutput() = 0.0; // инициализация выходных данных | ||
| } | ||
|
|
||
| bool TrapezoidIntegrationSequential::ValidationImpl() { | ||
| return (GetInput().b >= GetInput().a) && | ||
| (GetInput().n > 0); // проверка b >= a (границ интегрирования) и n > 0 (число разбиений) | ||
| } | ||
|
|
||
| bool TrapezoidIntegrationSequential::PreProcessingImpl() { | ||
| return true; | ||
| } | ||
|
|
||
| bool TrapezoidIntegrationSequential::RunImpl() { | ||
| double a = GetInput().a; | ||
| double b = GetInput().b; | ||
| int n = GetInput().n; | ||
|
|
||
| double h = (b - a) / n; | ||
| double integral_res = (Func(a) + Func(b)) / 2.0; | ||
|
|
||
| for (int i = 1; i < n; ++i) { | ||
| integral_res += Func(a + (i * h)); | ||
| } | ||
|
|
||
| GetOutput() = integral_res * h; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| bool TrapezoidIntegrationSequential::PostProcessingImpl() { | ||
| return true; | ||
| } | ||
|
|
||
| } // namespace kutergin_v_trapezoid_seq |
8 changes: 8 additions & 0 deletions
8
tasks/kutergin_v_trapezoid_method_of_integration/settings.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "tasks_type": "processes", | ||
| "tasks": | ||
| { | ||
| "seq": "enabled", | ||
| "mpi": "enabled" | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.