<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bvdmitri</id>
	<title>Алговики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bvdmitri"/>
	<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Bvdmitri"/>
	<updated>2026-10-22T20:10:06Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=19547</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=19547"/>
		<updated>2016-11-29T21:26:24Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм не использует и не подразумевает хранение общих данных между вычислительными единицами.&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
=== Master-Slave ===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для исследования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но, вследствие использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Master-Slave реализация ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов. Управляющий узел перестает успевать распределять задачи, поэтому большое число вычислительных узлов простаивает.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17545</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17545"/>
		<updated>2016-11-16T08:19:55Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Использование управляющего узла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм не использует и не подразумевает хранение общих данных между вычислительными единицами.&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
=== Master-Slave ===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для исследования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но, вследствие использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Master-Slave реализация ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов. Управляющий узел перестает успевать распределять задачи, поэтому большое число вычислительных узлов простаивает.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17544</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17544"/>
		<updated>2016-11-16T08:19:28Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Использование управляющего узла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм не использует и не подразумевает хранение общих данных между вычислительными единицами.&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для исследования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но, вследствие использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Master-Slave реализация ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов. Управляющий узел перестает успевать распределять задачи, поэтому большое число вычислительных узлов простаивает.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17279</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17279"/>
		<updated>2016-11-15T14:20:46Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Локальность данных и вычислений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм не использует и не подразумевает хранение общих данных между вычислительными единицами.&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17274</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17274"/>
		<updated>2016-11-15T14:15:33Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Исходный код */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17273</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17273"/>
		<updated>2016-11-15T14:15:11Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Исходный код */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17272</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17272"/>
		<updated>2016-11-15T14:15:01Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Исходный код */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17270</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17270"/>
		<updated>2016-11-15T14:12:44Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Исходный код */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17268</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17268"/>
		<updated>2016-11-15T14:12:17Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Использование управляющего узла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для управляющего узла.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;toccolours mw-collapsible mw-collapsed&amp;quot;&amp;gt;&lt;br /&gt;
Исходный код для вычислительных узлов.&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17267</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17267"/>
		<updated>2016-11-15T14:07:29Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Исходный код для управляющего узла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
===== Исходный код для управляющего узла =====&lt;br /&gt;
&lt;br /&gt;
{{Collapse|&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
===== Исходный код для вычислительных узлов =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17266</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17266"/>
		<updated>2016-11-15T14:05:33Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Использование управляющего узла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает плохую масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
===== Исходный код для управляющего узла =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Исходный код для вычислительных узлов =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17265</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17265"/>
		<updated>2016-11-15T14:03:44Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_farm_adaptive_integral_tests.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
===== Исходный код для управляющего узла =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Исходный код для вычислительных узлов =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17260</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17260"/>
		<updated>2016-11-15T13:46:25Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Исходную задачу можно разбить на более мелкие задачи делением отрезка. Каждая задача может быть решаться отдельно от остальных, за счет чего можно достичь высокой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17258</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17258"/>
		<updated>2016-11-15T13:41:55Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Деление на подотрезки */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация практически всегда показывает линейную масштабируемость.&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17257</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17257"/>
		<updated>2016-11-15T13:40:18Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Очередь задач на общей памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
Данная реализация показывает практически линейную масштабируемость, но из-за использования для параллельности только общей памяти процессора, максимальное количество вычислительных единиц ограничено количеством ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17256</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17256"/>
		<updated>2016-11-15T13:37:33Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
Для тестирования масштабируемости были проведены серии тестов с различными функциями. Функции и отрезки интегрирования выбирались таким образом, чтобы первоначальное время вычисление интеграла на последовательной версии было существенно большим. Далее проводились замеры времени для каждой реализации. Количество ядер выбиралось равным степени двойки, а также исходя из специфики самой реализации.&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
Характеристики вычислительного кластера: [http://users.parallel.ru/wiki/pages/22-config link].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17246</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17246"/>
		<updated>2016-11-15T13:04:03Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Mpi_shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17244</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17244"/>
		<updated>2016-11-15T12:54:35Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Очередь задач на общей памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|900px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17243</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17243"/>
		<updated>2016-11-15T12:54:19Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Очередь задач на общей памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
[[File:Shared_memory_adaptive_integral.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17222</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17222"/>
		<updated>2016-11-15T11:39:16Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Использование управляющего узла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная реализация&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17221</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17221"/>
		<updated>2016-11-15T11:38:44Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Использование управляющего узла ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm GitHub]&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для управляющего узла ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;Farm.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Farm::Farm(int nodes_num, double a, double b, double tau) {&lt;br /&gt;
    this-&amp;gt;nodes_num = nodes_num;&lt;br /&gt;
    this-&amp;gt;availableNodes = (int *) calloc((size_t) nodes_num, sizeof(int));&lt;br /&gt;
    this-&amp;gt;availableNodesCount = nodes_num - 1;&lt;br /&gt;
    this-&amp;gt;a = a;&lt;br /&gt;
    this-&amp;gt;b = b;&lt;br /&gt;
    this-&amp;gt;tau = tau;&lt;br /&gt;
    this-&amp;gt;answer = 0.0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;jobCount = 0;&lt;br /&gt;
    this-&amp;gt;jobs = (double *) calloc(FARM_JOBS_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    //Buf structure&lt;br /&gt;
    //0 - a&lt;br /&gt;
    //1 - b&lt;br /&gt;
    //2 - tau, if tau &amp;lt; 0, a = y or exit job&lt;br /&gt;
&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(FARM_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, FARM_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
    //Main node is always busy&lt;br /&gt;
    availableNodes[0] = 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::SendInitialJobs() {&lt;br /&gt;
    double shift = (b - a) / (nodes_num - 1);&lt;br /&gt;
    double *buf = (double *) calloc(3, sizeof(double));&lt;br /&gt;
    for (int i = 0; i &amp;lt; nodes_num - 1; i++) {&lt;br /&gt;
        buf[0] = a + shift * i;&lt;br /&gt;
        buf[1] = buf[0] + shift;&lt;br /&gt;
        buf[2] = tau;&lt;br /&gt;
&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i + 1, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
        availableNodes[i + 1] = 1;&lt;br /&gt;
        availableNodesCount -= 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::work() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    MPI_Status status;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        MPI_Recv(buf, 3, MPI_DOUBLE, MPI_ANY_SOURCE, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD, &amp;amp;status);&lt;br /&gt;
        int source = status.MPI_SOURCE;&lt;br /&gt;
&lt;br /&gt;
        if (buf[2] &amp;lt; 0.0) {&lt;br /&gt;
            //Node send result&lt;br /&gt;
            answer += buf[0];&lt;br /&gt;
            if (jobCount != 0) {&lt;br /&gt;
                buf[0] = jobs[(jobCount - 1) * 3 + 0];&lt;br /&gt;
                buf[1] = jobs[(jobCount - 1) * 3 + 1];&lt;br /&gt;
                buf[2] = jobs[(jobCount - 1) * 3 + 2];&lt;br /&gt;
                jobCount -= 1;&lt;br /&gt;
&lt;br /&gt;
                MPI_Bsend(buf, 3, MPI_DOUBLE, source, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
            } else {&lt;br /&gt;
                availableNodes[source] = 0;&lt;br /&gt;
                availableNodesCount += 1;&lt;br /&gt;
                if (availableNodesCount == (nodes_num - 1)) {&lt;br /&gt;
                    KillWorkers();&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            //Node send job&lt;br /&gt;
            if (availableNodesCount != 0) {&lt;br /&gt;
                for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
                    if (availableNodes[i] == 0) {&lt;br /&gt;
                        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
                        availableNodes[i] = 1;&lt;br /&gt;
                        availableNodesCount -= 1;&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                jobs[jobCount * 3 + 0] = buf[0];&lt;br /&gt;
                jobs[jobCount * 3 + 1] = buf[1];&lt;br /&gt;
                jobs[jobCount * 3 + 2] = buf[2];&lt;br /&gt;
                jobCount += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Farm::KillWorkers() {&lt;br /&gt;
    double buf[3];&lt;br /&gt;
    buf[2] = -1.0;&lt;br /&gt;
    for (int i = 1; i &amp;lt; nodes_num; i++) {&lt;br /&gt;
        MPI_Bsend(buf, 3, MPI_DOUBLE, i, JOB_SEND_TAG, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double Farm::Result() {&lt;br /&gt;
    return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Farm::~Farm() {&lt;br /&gt;
    free(jobs);&lt;br /&gt;
    int size = FARM_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Исходный код для вычислительных узлов ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;quot;Worker.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Worker::Worker(const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f): f(f) {&lt;br /&gt;
    this-&amp;gt;buf = (double *) calloc(16, sizeof(double));&lt;br /&gt;
    this-&amp;gt;message_buffer = (double *) calloc(WORKER_MPI_BUFFER_SIZE, sizeof(double));&lt;br /&gt;
    MPI_Buffer_attach(message_buffer, WORKER_MPI_BUFFER_SIZE * sizeof(double));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::work() {&lt;br /&gt;
    while (true) {&lt;br /&gt;
        GetJob();&lt;br /&gt;
        if (buf[2] &amp;gt; 0.0) {&lt;br /&gt;
            Calculate();&lt;br /&gt;
        } else {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::GetJob() {&lt;br /&gt;
    MPI_Recv(buf, 3, MPI_DOUBLE, 0, JOB_GET_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::Calculate() {&lt;br /&gt;
    double a = buf[0];&lt;br /&gt;
    double b = buf[1];&lt;br /&gt;
    double tau = buf[2];&lt;br /&gt;
&lt;br /&gt;
    while (true) {&lt;br /&gt;
        double x = simpson(a, b, 4);&lt;br /&gt;
        double y = simpson(a, b, 8);&lt;br /&gt;
&lt;br /&gt;
        if (fabs(x - y) &amp;lt; tau) {&lt;br /&gt;
            SendResult(y);&lt;br /&gt;
            break;&lt;br /&gt;
        } else {&lt;br /&gt;
            SaveJob(a, (a + b) / 2, tau / 2);&lt;br /&gt;
            a = (a + b) / 2;&lt;br /&gt;
            tau = tau / 2;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SaveJob(double a, double b, double tau) {&lt;br /&gt;
    double job[3];&lt;br /&gt;
    job[0] = a;&lt;br /&gt;
    job[1] = b;&lt;br /&gt;
    job[2] = tau;&lt;br /&gt;
    MPI_Bsend(job, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Worker::SendResult(double result) {&lt;br /&gt;
    double r[3];&lt;br /&gt;
    r[0] = result;&lt;br /&gt;
    r[1] = 0.0;&lt;br /&gt;
    r[2] = -1.0;&lt;br /&gt;
    MPI_Bsend(r, 3, MPI_DOUBLE, 0, JOB_SAVE_OR_RESULT_TAG, MPI_COMM_WORLD);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Worker::~Worker() {&lt;br /&gt;
    free(buf);&lt;br /&gt;
    int size = WORKER_MPI_BUFFER_SIZE * sizeof(double);&lt;br /&gt;
    MPI_Buffer_detach(message_buffer, &amp;amp;size);&lt;br /&gt;
    free(message_buffer);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
double Worker::simpson(double a, double b, int k) {&lt;br /&gt;
    double h = (b - a) / ((double) 2 * k);&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    for (int j = 1; j &amp;lt; 2 * k; j += 2) {&lt;br /&gt;
        answer += (f(a + h * (j - 1)) + 4 * f(a + h * j) +&lt;br /&gt;
                   f(a + h * (j + 1)));&lt;br /&gt;
    }&lt;br /&gt;
    return h * answer / 3;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17220</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17220"/>
		<updated>2016-11-15T11:36:42Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Очередь задач на общей памяти */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Деление на подотрезки ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double answer = 0.0;&lt;br /&gt;
    int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
    double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
    a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17219</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17219"/>
		<updated>2016-11-15T11:35:01Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/tbb GitHub]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17218</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=17218"/>
		<updated>2016-11-15T11:33:29Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
=== Очередь задач на общей памяти ===&lt;br /&gt;
Реализация для эффективного использования многомерного процессора. В данном случае вместо рекурсии используется очередь задач общая для всех ядер процессора.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода: &lt;br /&gt;
* Очень простая реализация&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Нет возможности использовать многопроцессорные кластеры, так как очередь задач находится в общей памяти.&lt;br /&gt;
* Масштабируемость достигается только при увеличении количества ядер в одном процессоре.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; разброс нагрузок на процессоры может оказаться значительным, что приведет к простою части ядер.&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16935</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16935"/>
		<updated>2016-11-14T10:57:58Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Существующие реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16934</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16934"/>
		<updated>2016-11-14T10:57:47Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Литература */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
===Деление на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки; может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами.&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;br /&gt;
* Якобовский М.В., Кулькова Е.Ю. &amp;quot;Решение задач на многопроцессорных вычислительных системах с разделяемой памятью.&amp;quot;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16928</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16928"/>
		<updated>2016-11-14T10:51:58Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Свойства алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является устойчивым&lt;br /&gt;
* Алгоритм является детерминированным&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
===Разделение на подотрезки===&lt;br /&gt;
Отрезок интегрирования делится на одинаковые по длине отрезки. Количество этих отрезков определяется числом доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&lt;br /&gt;
===Использование управляющего узла===&lt;br /&gt;
Можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16919</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16919"/>
		<updated>2016-11-14T10:48:41Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Литература */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive Quadrature Wikipedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16918</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16918"/>
		<updated>2016-11-14T10:48:28Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Литература */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_quadrature Adaptive_quadrature WikiPedia]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Simpson%27s_rule Simpson&#039;s Rule]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16917</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16917"/>
		<updated>2016-11-14T10:47:32Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Существующие реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method Adaptive Simpson&#039;s method]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16897</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16897"/>
		<updated>2016-11-14T10:32:33Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Как уже было сказано в разделе 1.7 алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&amp;lt;hr&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16896</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16896"/>
		<updated>2016-11-14T10:31:57Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Как уже было сказано в разделе 1.7 алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
—&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16895</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16895"/>
		<updated>2016-11-14T10:31:36Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Как уже было сказано в разделе 1.7 алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Участник:Bvdmitri|Bvdmitri]] ([[Обсуждение участника:Bvdmitri|обсуждение]]) 13:31, 14 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16893</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16893"/>
		<updated>2016-11-14T10:31:05Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Как уже было сказано в разделе 1.7 алгоритм имеет несколько возможных параллельных реализаций.&lt;br /&gt;
&lt;br /&gt;
В первом случае отрезок делиться на одинаковые по длине отрезки. Количество этих отрезков определяется количеством доступных процессоров. Каждый процессор считает определенный интеграл лишь на своем отрезке используя внутри себя очередь задач в общей памяти. &lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Простая реализация&lt;br /&gt;
* Хорошая масштабируемость на большом классе функций&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода&lt;br /&gt;
* Отсутсвие балансировки нагрузки, может случится ситуация, когда все процессоры, кроме одного завершили свою работу и ждут&lt;br /&gt;
&lt;br /&gt;
Во втором случае можно использовать управляющий узел, который будет распределять нагрузку между вычислительными узлами&lt;br /&gt;
&lt;br /&gt;
Плюсы данного подхода:&lt;br /&gt;
* Балансировка нагрузки&lt;br /&gt;
&lt;br /&gt;
Минусы данного подхода:&lt;br /&gt;
* Более сложная нагрузка&lt;br /&gt;
* Хорошая масштабируемость достигается на очень узком классе функций&lt;br /&gt;
* Плохая масштабируемость при очень большом количестве вычислительных узлов, управляющий узел перестает успевать распределять задачи из-за чего большое количество вычислительных узлов простаивают&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16885</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16885"/>
		<updated>2016-11-14T10:21:29Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число подотрезков, и каждый подотрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема очереди задач. Рекурсия заменяется на операции добавить и достать задачу из общей очереди задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16849</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16849"/>
		<updated>2016-11-13T23:56:58Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Литература */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число под-отрезков, и каждый под отрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема стека задач. Рекурсия заменяется на операции добавить и достать задачу из общего стека задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16847</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16847"/>
		<updated>2016-11-13T23:46:44Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Свойства алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число под-отрезков, и каждый под отрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема стека задач. Рекурсия заменяется на операции добавить и достать задачу из общего стека задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
* Алгоритм устроен таким образом, что шаг сетки необходимой для вычисления определенного интеграла с заданной точностью адаптируется под сложность функции.&lt;br /&gt;
* Алгоритм является детерминированным и устойчивым&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16844</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16844"/>
		<updated>2016-11-13T23:43:19Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число под-отрезков, и каждый под отрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема стека задач. Рекурсия заменяется на операции добавить и достать задачу из общего стека задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Во втором случае можно использовать управляющий узел для распределения задач между вычислительными узлами. В данном случае можно избежать ситуации, когда интегрируемая функция сильно осциллирует лишь на небольшом участке отрезка и большинство процессоров завершают свою работу раньше и ждут остальных.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_farm_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16837</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16837"/>
		<updated>2016-11-13T23:23:18Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
У алгоритма есть несколько возможностей для распараллеливания.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
В первом случае можно разбить отрезок на фиксированное число под-отрезков, и каждый под отрезок считать на одном процессоре с дальнейшим суммированием результатов с каждого процессора. Внутри процессора используется схема стека задач. Рекурсия заменяется на операции добавить и достать задачу из общего стека задач. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16834</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16834"/>
		<updated>2016-11-13T23:18:03Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|1200px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16833</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16833"/>
		<updated>2016-11-13T23:17:50Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|700px|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16832</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16832"/>
		<updated>2016-11-13T23:15:25Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
[[Файл:Mpi_tbb_adaptive_integral.png|center]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16831</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16831"/>
		<updated>2016-11-13T23:14:55Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
[Файл:Mpi_tbb_adaptive_integral.png]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Mpi_tbb_adaptive_integral.png&amp;diff=16830</id>
		<title>Файл:Mpi tbb adaptive integral.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Mpi_tbb_adaptive_integral.png&amp;diff=16830"/>
		<updated>2016-11-13T23:14:31Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16820</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16820"/>
		<updated>2016-11-13T22:44:31Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Схема реализации последовательного алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[Файл:Adaptive_integration_block.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16819</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16819"/>
		<updated>2016-11-13T22:42:11Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Схема реализации последовательного алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
[[file:Adaptive_integration_block.png|thumb|center|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от структуры интегрируемой функции, длины отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16816</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16816"/>
		<updated>2016-11-13T22:20:15Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Последовательная сложность алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Явную формулу сложности последовательного алгоритма выписать не представляется возможным, так как сложность зависит от многих факторов, таких как структура интегрируемой функции, длина отрезка, а так же необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16815</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16815"/>
		<updated>2016-11-13T22:17:04Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Последовательная сложность алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Оценить последовательную сложность алгоритма не представляется возможным, так как она зависит от множества факторов, включая сложность исходной функции, длина отрезка интегрирования, а так же заданная точность.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16814</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16814"/>
		<updated>2016-11-13T22:15:39Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Схема реализации последовательного алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16813</id>
		<title>Участник:Bvdmitri/Адаптивное интегрирование</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bvdmitri/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5&amp;diff=16813"/>
		<updated>2016-11-13T22:14:27Z</updated>

		<summary type="html">&lt;p&gt;Bvdmitri: /* Макростуктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Основные авторы описания: [[Участник:bvdmitri|Д.В.Багаев]]&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Адаптивное интегрирование&#039;&#039;&#039;  —  метод быстрого вычисления интеграла функции, который использует адаптивное изменение плотности сетки интегрирования. На промежутках, где функция гладкая, сетка разреженная,  на промежутках со значительными колебаниями значений сетка гуще для получения необходимой точности.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Формулы метода: &lt;br /&gt;
&lt;br /&gt;
# Интеграл функции вычисляется по квадратурной [https://ru.wikipedia.org/wiki/Формула_Симпсона формуле Симпсона] с числом узловых точек равным 4 и 8 на отрезке &amp;lt;math&amp;gt;[a, b] &amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычисляется разность найденных значений&lt;br /&gt;
## Если разность интегралов меньше либо равна желаемой точности интегрирования ε - заканчиваем процедуру.&lt;br /&gt;
## Если разность больше, то отрезок разбивается на два : &amp;lt;math&amp;gt;[a, \tfrac{a + b} {2}]&amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt;[\tfrac{a + b} {2}, b]&amp;lt;/math&amp;gt; и для каждого отрезка повторяется процедура 1, затем возвращается сумма полученных значений.&lt;br /&gt;
Алгоритм имеет рекурсивную природу.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основное время работы алгоритма приходится на рекурсивные вызовы, а также на вычисление определенных интегралов методом Симпсона на отрезках, полученных в результате рекурсивного разбиения.&lt;br /&gt;
&lt;br /&gt;
== Макростуктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
=== Входные данные ===&lt;br /&gt;
&lt;br /&gt;
* Функция.&lt;br /&gt;
* Отрезок интегрирования &amp;lt;math&amp;gt;[a, b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Желаемая точность интегрирования &amp;lt;math&amp;gt;\varepsilon&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Выходные данные ===&lt;br /&gt;
&lt;br /&gt;
* Определенный интеграл функции на отрезке [a, b] с точностью ε.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
=== Исходный код реализованного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Весь исходный код в статье можно найти в репозитории на [https://github.com/bvdmitri/aintegrate GitHub].&lt;br /&gt;
&lt;br /&gt;
=== Реализации на одном процессоре ===&lt;br /&gt;
&lt;br /&gt;
Простейшую рекурсивную реализацию данного алгоритма , которая не использует ресурсы параллелизма можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/sequential/SequentialMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;SequentialMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::SequentialMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double SequentialMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        return integrate(a, (a + b) / 2.0) + integrate((a + b) / 2.0, b);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
SequentialMethod::~SequentialMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Реализация с помощью библиотеки [https://www.threadingbuildingblocks.org Intel TBB] для более эффективного использования многоядерных процессоров можно посмотреть [https://github.com/bvdmitri/aintegrate/blob/master/src/method/tbb/TBBMethod.cpp здесь].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;quot;TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
TBBMethod::TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double TBBMethod::integrate(double a, double b) {&lt;br /&gt;
    double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = integrate(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = integrate((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
TBBMethod::~TBBMethod() {}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Параллельная реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Реализация с помощью MPI для межпроцессорного взаимодействия и [https://www.threadingbuildingblocks.org Intel TBB]  для внутрипроцессорного взаимодействия ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;quot;MPI_TBBMethod.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::MPI_TBBMethod(double eps, const std::function&amp;lt;double(double)&amp;gt; &amp;amp;f) : IntegrationMethod(eps, f) {}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::tbb_run(double a, double b) {&lt;br /&gt;
	double x = simpson(a, b, 4);&lt;br /&gt;
    double y = simpson(a, b, 8);&lt;br /&gt;
    if (fabs(x - y) &amp;lt; eps) {&lt;br /&gt;
        return y;&lt;br /&gt;
    } else {&lt;br /&gt;
        tbb::task_group group;&lt;br /&gt;
&lt;br /&gt;
        group.run([&amp;amp;] {&lt;br /&gt;
            x = tbb_run(a, (a + b) / 2.0);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        group.run_and_wait([&amp;amp;] {&lt;br /&gt;
            y = tbb_run((a + b) / 2.0, b);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return x + y;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
double MPI_TBBMethod::integrate(double a, double b) {&lt;br /&gt;
	double answer = 0.0;&lt;br /&gt;
	int nodes = 1;&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;nodes);&lt;br /&gt;
&lt;br /&gt;
    int node_id = 0;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;node_id);&lt;br /&gt;
&lt;br /&gt;
	double h = (b - a) / (double) nodes;&lt;br /&gt;
&lt;br /&gt;
	a = a + h * node_id;&lt;br /&gt;
    b = a + h;&lt;br /&gt;
&lt;br /&gt;
    double node_answer = tbb_run(a, b);&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
    if (node_id == 0) {&lt;br /&gt;
    	answer += node_answer;&lt;br /&gt;
        for (int i = 1; i &amp;lt; nodes; i++) {&lt;br /&gt;
            MPI_Recv(&amp;amp;node_answer, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
            answer += node_answer;&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        MPI_Send(&amp;amp;node_answer, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    MPI_Barrier(MPI_COMM_WORLD);&lt;br /&gt;
&lt;br /&gt;
	return answer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MPI_TBBMethod::~MPI_TBBMethod() {}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данной реализации отрезок [a, b] делится на число доступных процессоров, каждый отрезок считается на своем процессоре, рекурсия заменяется на [https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming систему] задач библиотеки Intel MPI для более эффективного использования многоядерных процессоров.&lt;br /&gt;
&lt;br /&gt;
==== Реализация только с помощью MPI ====&lt;br /&gt;
&lt;br /&gt;
[https://github.com/bvdmitri/aintegrate/tree/master/src/method/mpi_farm Исходный код]&lt;br /&gt;
&lt;br /&gt;
В данной реализации рекурсия заменяется на систему задач, каждая задача представляет собой подсчет интеграла на отрезке, в случае если необходимая точность не достигнута (см. Математическое описание алгоритма), создаются две новые задачи с меньшими отрезками и распределяются между другими процессорами. Данная реализация в теории решает проблему неравномерности гладких и не гладких участков функции, в случае когда большая часть участков гладкая и лишь небольшой участок функции сильно осциллирует.&lt;br /&gt;
&lt;br /&gt;
=== Тесты производительности ===&lt;br /&gt;
&lt;br /&gt;
Все тесты проводились на [https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер) суперкомпьютере Ломоносов].&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Тестировались две [https://github.com/bvdmitri/aintegrate/blob/master/functions.h функции] разной степени сложности.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Из тестов видно, что параллельная реализация с использованием технологий MPI и Intel TBB показывает прекрасную масштабируемость, в то же время версия на чистом MPI с управляющим узлом сильно проигрывает. Это связано с тем, что при синхронизации задач между процессорами происходит много пересылок сообщений MPI, в сравнении с вычислением значений функции в узловых точках. Это сильно замедляет работу программы в целом, более того при увеличении количества узлов управляющий узел не справляется с очень большим потоком сообщений от вычислительных узлов.&lt;br /&gt;
&lt;br /&gt;
==== MPI + TBB ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_tbb_1.png|thumb|center|500px|Рисунок 1. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_tbb_2.png|thumb|center|500px|Рисунок 2. Результаты масштабируемости для второй функции]]&lt;br /&gt;
&lt;br /&gt;
==== MPI Farm ==== &lt;br /&gt;
&lt;br /&gt;
[[file:Mpi_farm_1.png|thumb|center|500px|Рисунок 3. Результаты масштабируемости для первой функции]]&lt;br /&gt;
[[file:Mpi_farm_2.png|thumb|center|500px|Рисунок 4. Результаты масштабируемости для второй функции]]&lt;/div&gt;</summary>
		<author><name>Bvdmitri</name></author>
	</entry>
</feed>