<?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=IgorS</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=IgorS"/>
	<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/IgorS"/>
	<updated>2026-10-22T19:06:44Z</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=21530</id>
		<title>Участник:Vika3410/Метод Разделяй и властвуй</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=21530"/>
		<updated>2017-01-09T10:28:53Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
&lt;br /&gt;
Авторы статьи: [[Участник:NikoSergAlex|Сергеев Никита]] (группа 614) (1.1, 1.2, 1.5, 1.6, 1.7, 1.8, 1.10, 2.4), [[Участница:Vika3410|Стрельцова Виктория]] (группа 614) (1.2, 1.3, 1.4, 1.6, 1.7, 1.9, 1.10, 2.4)&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Метод &amp;quot;Разделяй и властвуй&amp;quot; вычисления собственных значений и векторов трёхдиагональной матрицы&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt; c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
| parallel_complexity = &amp;lt;math&amp;gt; O(n^{2.3}) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
крайне редко: &amp;lt;math&amp;gt;  O(n^{2}) &amp;lt;/math&amp;gt; &lt;br /&gt;
| pf_width          = &lt;br /&gt;
| input_data        =  &amp;lt;math&amp;gt; 2n + 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       =  &amp;lt;math&amp;gt; n(n + 1) &amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - является наиболее быстрым из существующих методов вычисления всех собственных значений и собственных векторов трехдиагональной матрицы, начиная с порядка n, который примерно равен 26. (Точное значение этого порогового порядка в конкретном случае зависит от компьютера.) &lt;br /&gt;
&lt;br /&gt;
Численно устойчивая реализация данного метода весьма не тривиальна, так как не смотря на то, что впервые метод был предложен в 1981 г., &amp;quot;правильный&amp;quot; способ реализации метода был найден лишь в 1992 г. Этот способ был воплощен при помощи LAPACK-программ ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В этих программах стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка выше 25. &lt;br /&gt;
&lt;br /&gt;
Для матриц порядка ниже 25 (или в тех случаях, когда требуются только собственные значения) автоматически происходит переход к методу QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В предположении, что известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt;T_{i}=Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. На самом деле, они будут вычисляться рекурсивно тем же самым алгоритмом. &lt;br /&gt;
&lt;br /&gt;
Можно установить связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Получается:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} L_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt; u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v &amp;lt;/math&amp;gt; - матрица, в первой строке которой находятся элементы последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt;, а во второй - элементы первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;, так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;.&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет такие же собственные значения, что и подобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} L_{1} &amp;amp; 0 \\ 0 &amp;amp; L_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
В дальнейшем предполагаем, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по не возрастанию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, потребуется вычислить её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Так как &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденная, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - является собственным значением. Можем заметить, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной путём добавления матрицы ранга 1. Определитель такой матрицы не сложно вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 1.&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Следовательно, получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; являются корнями так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны между собой и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа, который показан на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 1.jpg|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
По рисунку можно заметить, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для данного графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Так как &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Из этого следует, что корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень будет находиться слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, которая является монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который будет быстро и монотонно сходиться к каждому из корней, при условии, что начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать тот факт, что на практике метод сходится к каждому собственному значению за строго ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, следовательно для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 2.&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = &lt;br /&gt;
&lt;br /&gt;
(D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u = &lt;br /&gt;
&lt;br /&gt;
u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u]= &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = u + \alpha(D - \alpha I)^{-1}u - u = \alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
поскольку &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Что и требовалось доказать.&lt;br /&gt;
&lt;br /&gt;
Получается, что для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, данная формула не обеспечивает нам численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Учёным потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Подробнее детали будут обсуждаться ниже в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это будет не так, то вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а следовательно &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: в случае, если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;&#039;дефляции&#039;&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, в том случает, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, следовательно при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; применяя левое умножение &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не будет требовать никаких затрат. Аналогичное упрощение возможно в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, которая связана с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
Далее пойдет речь о решении &#039;&#039;&#039;векового уравнения&#039;&#039;&#039;, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 2. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.2 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 2.jpg|thumb|center|800px|Рис. 2. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
&amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (*)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (*) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt; удовлетворяла условиям&lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} \equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения будет являться вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов при помощи умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt;. Эта операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём будет говориться ниже, в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
&lt;br /&gt;
Данной операции предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
В разделе [[#Информационный граф]] описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Рассмотрим каким именно образом происходит вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы при помощи стратегии &amp;quot;разделяй и властвуй&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В случае, &#039;&#039;&#039;если&#039;&#039;&#039; &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
1.   Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотрим сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов, которое потребуется при обработке матрицы размера &amp;lt;math&amp;gt; n * &lt;br /&gt;
n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. В таком случае &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматривать как плотные матрицы и использовать стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке будет равна 1. Следовательно, именно это умножение и будет составлять наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решив это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно оказывается гораздо меньше 1, так как матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, которое называется дефляцией, и которое было описано выше.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
На рисунке 3 изображена структура алгоритма.&lt;br /&gt;
&lt;br /&gt;
[[File:Sergeev_5.png|thumb|center|left|1000px|Рис. 3. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Операции: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - операция Divide разделения матрицы &amp;lt;math&amp;gt; T &amp;lt;/math&amp;gt; на матрицы &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Индексы - размерность &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_{k}&amp;lt;/math&amp;gt; - операция Conquer сбора матрицы &amp;lt;math&amp;gt; D &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; L_{k} &amp;lt;/math&amp;gt; - операция поиска собственных значений &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S^{&#039;} &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
В структуре алгоритма имеем два параллельных блока - вызов рекурсивных функций &#039;&#039;dc_eig&#039;&#039; для вычисления собственных значений и векторов матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&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;
В случае реализации без параллелизма, функции &#039;&#039;dc_eig&#039;&#039; отрабатывают последовательно - сначала со входными параметрами &amp;lt;math&amp;gt;T_{1}, Q_{1}, \Lambda_{1}&amp;lt;/math&amp;gt;, затем - &amp;lt;math&amp;gt;T_{2}, Q_{2}, \Lambda_{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, благодаря параллелизму высота алгоритма параллельного составляет &amp;lt;math&amp;gt;log_{2}(n)&amp;lt;/math&amp;gt;, тогда как при последовательной реализации высота алгоритма равна сумме геометрической прогрессии &amp;lt;math&amp;gt;1+2+4+8+...+2^{n-1} = 2^{n} - 1&amp;lt;/math&amp;gt;, что значительно сокращает число итераций алгоритма.&lt;br /&gt;
&lt;br /&gt;
Поскольку функции являются рекурсивными, их параллельный вызов даст значительный выигрыш во времени.&lt;br /&gt;
&lt;br /&gt;
При размерности матрицы больше 3 параллельная реализация позволяет сократить количество этапов работы алгоритма больше, чем в два раза.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: трёхдиагональная симметрическая матрица, описанная в разделе [[#Математическое описание алгоритма]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt; 2n + 1 &amp;lt;/math&amp;gt; (в силу симметричности достаточно хранить только диагональ и над/под диагональные элементы).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: Вектор собственных значений длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; собственных векторов длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; , соответствующих собственным значениям исходной матрицы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;n(n + 1) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Данный алгоритм является самым быстрым алгоритмом среди существующих: Разделяй и властвуй, QR, Бисекция и обратная итерация.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Последовательная сложность данного алгоритма:&#039;&#039;&#039; &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Она удовлетворяет соотношению  &amp;lt;math&amp;gt;t(n) = n^3 +2t(\frac{n}{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Параллельная сложность алгоритма (за счет дефляции):&#039;&#039;&#039; &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;;  в крайне редких случаях: &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Особенности алгоритма:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
1. Дефляция ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Адаптированный метод Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&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;quot;Ломоносов&amp;quot; с использованием 1, 2, 4, 8, 16 процессоров на матрицах размера от 4 до 4096. Реализация произведена на библиотеке OpenMP.&lt;br /&gt;
&lt;br /&gt;
Общая эффективность программы увеличивается при увеличении числа процессоров (за счет сокращения времени работы программы) и уменьшается при увеличении размера матрицы (за счет увеличения времени обработки входных данных). При одновременном увеличении числа процессоров и размерности матрицы общая эффективность падает.&lt;br /&gt;
&lt;br /&gt;
Ниже приведена схема зависимости времени работы программы от размера матрицы при разном числе процессоров. При размере матрицы от 4 до 1024 время работы очень мало, поэтому эти данные не указаны на схеме.&lt;br /&gt;
&lt;br /&gt;
[[File:DivAndConq11.png|thumb|center|left|1000px|Рис. 4. Зависимость времени работы от размера матрицы]]&lt;br /&gt;
&lt;br /&gt;
Параметры компиляции и запуска: &lt;br /&gt;
&lt;br /&gt;
 ssh compiler &lt;br /&gt;
 module add slurm&lt;br /&gt;
 module add openmpi/1.5.5-icc&lt;br /&gt;
 g++ _scratch/DivideAndConquer.c++ -fopenmp -std=c++0x -o DC &lt;br /&gt;
 sbatch -p test -n1 run ./DC&lt;br /&gt;
&lt;br /&gt;
Код реализации: https://drive.google.com/open?id=0B0G2UV7cuLwHQk9xd1lyeDNJOXc&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Единственный найденный пример реализации описан в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot; ]   ([3] с. 7, 19-20).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
[1] Дж. Деммель, «Вычислительная линейная алгебра» //С. 230-235&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.cscamm.umd.edu/tadmor/pub/linear-stability/Gill_Tadmor_SISC90.pdf Doron Grill and Eitan Tadmor AN &amp;lt;math&amp;gt;O(N2)&amp;lt;/math&amp;gt; METHOD FOR COMPUTING THE EIGENSYSTEM OF &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;x&amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; SYMMETRIC TRIDIAGONAL MATRICES BY THE DIVIDE AND CONQUER APPROACH]&lt;br /&gt;
&lt;br /&gt;
[3] [http://www.netlib.org/utk/people/JackDongarra/PAPERS/104_1999_a-parallel-divide-and-conquer-algorithm.pdf Francoise Tisseury, Jack Dongarra, A Parallel Divide and Conquer Algorithm for the Symmetric Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[4] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[5] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=21492</id>
		<title>Участник:Timokhinivan/Быстрое преобразование Фурье</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=21492"/>
		<updated>2016-12-26T07:59:33Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразование Фурье&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Автор описания: [[U:Timokhinivan|Тимохин И.В.]], ВМК МГУ.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
В общем случае многомерное преобразование Фурье соответствует умножению на матрицу преобразования по каждой из координат, что для массива данных &amp;lt;math&amp;gt;N_1\times N_2 \times \cdots \times N_m&amp;lt;/math&amp;gt; означает выполнение &amp;lt;math&amp;gt;\sum_{i=1}^m \prod_{k\neq i} N_k&amp;lt;/math&amp;gt; одномерных преобразований. Ключевым наблюдением, приводящим к быстрому алгоримту для вычисления преобразования, является возможность свести (с некоторыми оговорками) одномерное преобразование к двумерному, которое для &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; требует &amp;lt;math&amp;gt;m + n&amp;lt;/math&amp;gt; рекурсивных вызовов, что, при применении такого метода на всех уровнях рекурсии, даёт общую сложность алгоритма &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Преобразование Фурье задаётся следующей формулой:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y_k = \sum_{l = 0}^{N-1} X_l \epsilon^{kl}_N,\qquad k = \overline{0,N-1}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;\epsilon_N = e^{-\frac{2i\pi}{N}}&amp;lt;/math&amp;gt; — первообразный корень&lt;br /&gt;
из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (т.е. &amp;lt;math&amp;gt;\epsilon_N&amp;lt;/math&amp;gt; является корнем из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, но не является корнем никакой меньшей степени). Число &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; при этом называется порядком преобразования.&lt;br /&gt;
&lt;br /&gt;
Таким образом, преобразование Фурье является линейным и задаётся&lt;br /&gt;
матрицей &amp;lt;math&amp;gt;F = \left\{ \epsilon^{kl}_N \right\}_{k,l = 0}^{N-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Многомерное преобразование Фурье ====&lt;br /&gt;
Естественным обобщением описанного одномерного преобразования Фурье является многомерный вариант:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y(l_1,\ldots,l_d) = \sum_{k_1 = 0}^{N_1-1} \cdots \sum_{k_d = 0}^{N_d-1} X(k_1,\ldots,k_d) \prod_{j=1}^{d} \epsilon_{N_j}^{l_j k_j}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Многомерное преобразование Фурье эквивалентно последовательному выполнению одномерных преобразований по каждому измерению; при использовании для этого рекурсивного алгоритма, общая сложность вычислений также составляет &amp;lt;math&amp;gt;O(N\log N)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;N = \prod_{j=1}^d N_j&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Сведение к (почти) двумерному преобразованию (алгоритм Кули-Тьюки) ====&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — составное, т.е. &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt;, то возможно&lt;br /&gt;
существенно сократить вычислительные расходы за счёт сведения к&lt;br /&gt;
двумерному преобразованию Фурье.  А именно, положим &amp;lt;math&amp;gt;X(l_1, l_2)&lt;br /&gt;
= X_{l_1 n + l_2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt; Y(k_1, k_2) = Y_{k_1 m + k_2}&amp;lt;/math&amp;gt;,&lt;br /&gt;
где &amp;lt;math&amp;gt;l_1, k_2 = \overline{0, m-1}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;l_2, k_1 =&lt;br /&gt;
\overline{0, n-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В этом случае&amp;lt;ref name=&amp;quot;bachv&amp;quot;&amp;gt;Н.С. Бахвалов, Н.П. Жидков, Г.М. Кобельков, &#039;&#039;Численные методы&#039;&#039;, 2003 г., ISBN: 5-94774-060-5&amp;lt;/ref&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{align}&lt;br /&gt;
    Y(k_1, k_2) &amp;amp;=&amp;amp; \sum_{l_2=0}^{n-1} (\epsilon^{k_2 l_2}_N \hat{X}(k_2, l_2)) \epsilon^{k_1 l_2}_n \\&lt;br /&gt;
    \hat{X}(k_2, l_2) &amp;amp;=&amp;amp; \sum_{l_1=0}^{m-1} X(l_1, l_2) \epsilon^{k_2 l_1}_m&lt;br /&gt;
  \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это даёт следующий алгоритм вычисления преобразования:&lt;br /&gt;
&lt;br /&gt;
# Записываем исходный вектор в матрицу &amp;lt;math&amp;gt;m\times n&amp;lt;/math&amp;gt; по строкам.&lt;br /&gt;
# Применяем к каждому столбцу преобразование Фурье.&lt;br /&gt;
# Умножаем элемент в позиции &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;\epsilon^{ij}_N&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Применяем к каждой строке преобразование Фурье.&lt;br /&gt;
# Результат записан в получившейся матрице по столбцам.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм уже даёт существенный выигрыш по сравнению с обычным&lt;br /&gt;
умножением матрицы на вектор: &amp;lt;math&amp;gt; O(m^2 n + n^2 m) &amp;lt;/math&amp;gt; против&lt;br /&gt;
&amp;lt;math&amp;gt;O(m^2 n^2)&amp;lt;/math&amp;gt;.  Однако наилучших результатов можно добиться,&lt;br /&gt;
если применять этот алгоритм рекурсивно на этапах 2 и 4.&lt;br /&gt;
&lt;br /&gt;
Так, если &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;, то целесообразно&lt;br /&gt;
на каждом уровне рекурсии «отщеплять» одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (уровней рекурсии при этом будет &amp;lt;math&amp;gt;K \sim \log N&amp;lt;/math&amp;gt;).  В этом&lt;br /&gt;
случае задача сводится к вычислению &amp;lt;math&amp;gt; \prod_{i\neq j} p_i &amp;lt;/math&amp;gt;&lt;br /&gt;
преобразований Фурье порядка &amp;lt;math&amp;gt;p_j&amp;lt;/math&amp;gt; для всех&lt;br /&gt;
&amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;.  При небольших &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (например, 2), это&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; Y_0 = \sum_{l=0}^{N-1} X_l&amp;lt;/math&amp;gt; вычисляется непосредственно.&lt;br /&gt;
# Любым способом вычисляется &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; — первообразный корень мультипликативной группы поля &amp;lt;math&amp;gt;\mathbb{Z}_{N}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# &amp;lt;math&amp;gt; Y_{g^{N-p} \bmod N} = X_0 + \sum_{q=0}^{N-2} X_{g^q \bmod N} \epsilon_N^{g^{q-p}\bmod N} &amp;lt;/math&amp;gt;.  &lt;br /&gt;
&lt;br /&gt;
Последнее выражение представляет собой свёртку и вычисляется с помощью всё того же преобразования Фурье.  В принципе, это можно проделывать непосредственно, поскольку порядок свёртки &amp;lt;math&amp;gt;N - 1&amp;lt;/math&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;
&amp;lt;math&amp;gt;O(mn (\log m + \log n))&amp;lt;/math&amp;gt; операций против&lt;br /&gt;
&amp;lt;math&amp;gt;O(mn)&amp;lt;/math&amp;gt; для умножения на поправочные коэффициенты (шаг 3).&lt;br /&gt;
&lt;br /&gt;
То же верно и для алгоритма в целом в случае &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;;&lt;br /&gt;
а именно, суммарно наибольшие вычислительные затраты связаны с&lt;br /&gt;
вычислением преобразований Фурье порядка &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;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;n&amp;lt;/math&amp;gt; рекурсивных вызовов алгоритма.&lt;br /&gt;
# Умножение всех элементов рабочего вектора на поправочные коэффициенты.&lt;br /&gt;
# Ещё &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рекурсивных вызовов.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Псевдокод (для метода Кули-Тьюки):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;fft(X, N)&lt;br /&gt;
begin&lt;br /&gt;
  (m, n) = factor(N)&lt;br /&gt;
&lt;br /&gt;
  if (n == 1) // Предполагается, что n &amp;lt;= m&lt;br /&gt;
    direct_fft(X, N) // Через умножение на матрицу&lt;br /&gt;
&lt;br /&gt;
  // 1&lt;br /&gt;
  transpose(X, m, n) //Транспонирует X как матрицу m x n, хранящуюся по строкам&lt;br /&gt;
   &lt;br /&gt;
  // 2&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    fft(X(i * m : (i + 1) * m - 1), m)&lt;br /&gt;
&lt;br /&gt;
  // 3&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    do j = 0, m - 1&lt;br /&gt;
      X(i * m + j) *= eps(i * j, N)&lt;br /&gt;
&lt;br /&gt;
  // 4&lt;br /&gt;
  transpose(X, n, m)&lt;br /&gt;
&lt;br /&gt;
  // 5&lt;br /&gt;
  do j = 0, m - 1&lt;br /&gt;
    fft(X(j * n : (j + 1) * n - 1), n)&lt;br /&gt;
&lt;br /&gt;
  // 6&lt;br /&gt;
  transpose(X, m, n)&lt;br /&gt;
end&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь &amp;lt;code&amp;gt;eps&amp;lt;/code&amp;gt; может как вычислять соответствующие коэффициенты, так и брать их из заранее заготовленной таблицы.  Последний способ требует увеличения затрат по памяти, но позволяет существенно сэкономить на вычислениях, а потому является предпочтительным.&lt;br /&gt;
&lt;br /&gt;
Кроме того, шаги 3 и 4 можно переставлять произвольным образом (с очевидными изменениями в шаге 3).&lt;br /&gt;
&lt;br /&gt;
Особо следует отметить, что если порядок элементов в выходном массиве не важен, шаг 6 можно пропустить.  &lt;br /&gt;
&lt;br /&gt;
Так, например, если преобразование Фурье используется для вычисления свёртки векторов, то конкретный порядок элементов в их образах Фурье совершенно не важен, лишь бы он был одинаков для обоих векторов.  В этом случае в прямом преобразовании можно опустить шаг 6, а в обратном — 1.&lt;br /&gt;
&lt;br /&gt;
Если необходимо учитывать случай большого простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (что может и не требоваться, в зависимости от задачи), то в первом условном операторе следует дополнительно проверить величину &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; и, если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; большое, переключиться на алгоритм Радера.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Непосредственно из описания алгоритма получаем, что если сложность&lt;br /&gt;
вычисления преобразования Фурье обозначить &amp;lt;math&amp;gt;f_N&amp;lt;/math&amp;gt;, то&lt;br /&gt;
&amp;lt;math&amp;gt;f_{mn} = n f_{m} + m f_{n} + mn&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N = \prod_{i=1}^K p_i&amp;lt;/math&amp;gt;, и на каждом шаге&lt;br /&gt;
«отщеплять» от него одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt;, а преобразование Фурье для&lt;br /&gt;
&amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; вычислять «в лоб», то общее количество операций&lt;br /&gt;
составит &amp;lt;math&amp;gt;O\left(N\sum_{i=1}^K p_i\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В частности, если &amp;lt;math&amp;gt;p_i \leq P&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;K \leq \log_P N&amp;lt;/math&amp;gt;&lt;br /&gt;
и для сложности получаем &amp;lt;math&amp;gt;O(NP\log_P N)&amp;lt;/math&amp;gt;.  Полагая&lt;br /&gt;
&amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; фиксированным, получаем заявленную сложность &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
[[Файл:FourierRecursiveGraph.svg|center|thumb|600px|&#039;&#039;&#039;Рисунок 1.&#039;&#039;&#039; Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 15&amp;lt;/math&amp;gt;. Исходные данные обозначены фиолетовым, результат — красным.  Преобразования Фурье для &amp;lt;math&amp;gt;m = 5&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n = 3&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты представлено оранжевыми узлами.]]&lt;br /&gt;
&lt;br /&gt;
Каждый уровень рекурсивного вызова состоит из трёх этапов: рекурсивный вызов по столбцам, умножение на поправочные коэффициенты и рекурсивного вызова по строкам. Таким образом для высоты ЯПФ имеем&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 h(mn) = 1 + h(m) + h(n)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для «элементарных» БПФ высота ЯПФ будет та же, что и для [[Умножение плотной матрицы на вектор|умножения матрицы на вектор]].&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учётом разложения &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;h(N) = O\left(\sum_{i=1}^K p_i\right) = O(P \log_P N) = O(\log N)&amp;lt;/math&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;
&amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n&amp;lt;/math&amp;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;N&amp;lt;/math&amp;gt;.  Однако в частных случаях, возможны более компактные представления для параметров и результатов.&lt;br /&gt;
&lt;br /&gt;
Для описания этих случаев нам понадобится матрица&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Q = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;   \\ &lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp; 1 \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp; 1 &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp; \cdots &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp; 1 &amp;amp;        &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp; 1 &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — вещественный, то &amp;lt;math&amp;gt;Y = Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — чисто мнимый, то &amp;lt;math&amp;gt;Y = -Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — вещественный.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = -Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — чисто мнимый.&lt;br /&gt;
&lt;br /&gt;
В каждом случае, &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; требуют для своего хранения в два раза меньше памяти, чем в общем случае.&lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии дополнительной информации о входном векторе, возможно сократить объём памяти используемый для хранения входных и выходных данных.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
Обозначая матрицу преобразования &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, имеем&amp;lt;ref name=&amp;quot;voevodtyrt&amp;quot;&amp;gt;В.В. Воеводин, Е.Е. Тыртышников, &#039;&#039;Вычислительные процессы с теплицевыми матрицами&#039;&#039;, Москва, «Наука», 1987 г.&amp;lt;/ref&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;F = F^T&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^{-1} = \tfrac{1}{N} F^*&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^* = QF &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Свойство 2 представляет особый интерес, поскольку позволяет вычислять обратное преобразование Фурье по тем же алгоритмам, что и прямое, поскольку &amp;lt;math&amp;gt;F^*&amp;lt;/math&amp;gt; представляет собой матрицу того же вида, что и &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, но с &amp;lt;math&amp;gt;\bar{\epsilon}&amp;lt;/math&amp;gt; вместо &amp;lt;math&amp;gt;\epsilon&amp;lt;/math&amp;gt;.  В сущности, свойство 3 позволяет свести задачу к исходной непосредственно, но ценой дополнительных пересылок.&lt;br /&gt;
&lt;br /&gt;
Кроме того, из второго свойства следует&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 \|F x\|_2 = \sqrt{N} \|x\|_2&lt;br /&gt;
&amp;lt;/math&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;\epsilon^{l}_{n} = \epsilon^{lm}_{mn} = \epsilon^{lm}_{N}&amp;lt;/math&amp;gt;, все необходимые рекурсивному вызову значения поворотных коэффициентов уже имеются в используемом в текущем вызове массиве, а потому его достаточно подготовить один раз на весь алгоритм.&lt;br /&gt;
&lt;br /&gt;
==== Нерекурсивный случай ====&lt;br /&gt;
&lt;br /&gt;
На нижнем уровне рекурсии преобразование Фурье вычисляется путём умножения на матрицу.  Поскольку этот алгоритм, вообще говоря, более пригоден для векторизации, чем рекурсивная реализация, имеет смысл не разбивать &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; на &#039;&#039;совсем&#039;&#039; мелкие множители (&amp;lt;math&amp;gt;p_i \equiv 2&amp;lt;/math&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;
&#039;&#039;Многие&#039;&#039; используемые в алгоритме данные зависят только от порядка преобразования — массив поворотных коэффициентов, факторизация &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; в алгоритме Радера, конкретная задаваемая им перестановка и оптимальный порядок свёртки там же.  Вычисление некоторых из них сопоставимо по сложности со всем остальным алгоритмом.  Имея это ввиду, если планируется вычисление нескольких преобразований Фурье одного порядка, имеет смысл подготовить все эти данные заранее.&lt;br /&gt;
&lt;br /&gt;
==== Численная устойчивость ====&lt;br /&gt;
&lt;br /&gt;
Поскольку все преобразования в алгоритме являются, с точностью до константы &amp;lt;math&amp;gt;\leq \sqrt{N}&amp;lt;/math&amp;gt;, унитарными, катастрофического роста ошибок округления не предвидится, а потому все вычисления возможно проводить с целевой точностью.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
[[Файл:IT_FFTMemaccess.png|center|thumb|800px|Рисунок 2. Профиль обращений в память простого алгоритма Кули-Тьюки с &amp;lt;math&amp;gt;p = (2, 2, 3, 5, 2)&amp;lt;/math&amp;gt;. Сверху вниз: вспомогательная память для прямого умножения матрицы на вектор, память для хранения поворотных коэффициентов, рабочий вектор. Реализация не использует транспонирования векторов в явном виде.]]&lt;br /&gt;
&lt;br /&gt;
Как видно, алгоритм в целом обладает невысокой локальностью, что связано с его структурой (множество рекурсивных шагов, каждый из которых охватывает существенную часть рабочего вектора) и организацией доступа к поворотным коэффициентам. Некоторого улучшения локальности можно добиться, если транспонировать рабочую «матрицу» в памяти, но это либо требует дополнительной памяти, либо, если транспонировать «на месте», существенно усложняет алгоритм.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В случае составного &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; с &#039;&#039;достаточно&#039;&#039; большими делителями, наиболее очевидным и простым вариантом является распределение по процессорам рекурсивных вызовов алгоритма Кули-Тьюки. В этом случае на весь алгоритм приходится только три пересылки, но зато они проходят по схеме «все-со-всеми» (транспонирование матрицы) и не могут выполняться параллельно с вычислениями.&lt;br /&gt;
&lt;br /&gt;
Отдельно заметим, что, как уже упоминалось, первой либо последней группы пересылок можно избежать, если порядок элементов результата не важен.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; простое либо один из множителей в &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; всегда мал, ситуация усложняется. В этом случае придётся применять алгоритм Радера на межпроцессорном уровне, что связано с более сложной схемой пересылок и большим их количеством (т.к. приходится осуществлять преобразование Фурье дважды: прямое и обратное). &lt;br /&gt;
&lt;br /&gt;
В связи с этим, некоторые существующие реализации (например, Intel® MKL) просто не поддерживают этот случай. Впрочем, некоторые реализации не поддерживают большие простые делители &#039;&#039;вовсе&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
Хотя, как уже было указано, алгоритм поддаётся реализации с относительно небольшим числом пересылок, следует отметить, что вычислительна нагрузка на отдельные вычислительные ядра невелика и сопоставима с объёмом пересылок.  При этом сами пересылки достаточно объёмны (весь рабочий вектор), нелокальны (все-со-всеми), и не могут проводиться одновременно с вычислениями.  По этим причинам, ожидать от алгоритма хорошей масштабируемости не приходится, несмотря на «хорошие» свойства информационного графа.  Это и подтверждается экспериментом:&lt;br /&gt;
&lt;br /&gt;
[[Файл:TIFFTEfficiency.png|center|thumb|800px|Рисунок 3. Эффективность распараллеливания быстрого преобразования Фурье, выраженная как отношение времени работы на одном процессоре к произведению времени работы на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; процессорах на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;.  &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — порядок преобразования.]]&lt;br /&gt;
&lt;br /&gt;
При этом, однако, умеренного ускорения всё же удаётся добиться:&lt;br /&gt;
[[Файл:TIFFTSpeedup.png|center|thumb|800px|Рисунок 4.  Ускорение, получаемое при распараллеливании преобразования Фурье.  Обозначения те же, что и на рис. 3]]&lt;br /&gt;
&lt;br /&gt;
В общем, можно заметить, что, если возникла необходимость вычислять преобразование Фурье параллельно, добавление вычислительных узлов является целесообразным, однако такой ситуации следует по возможности избегать, поскольку ресурсы параллельной системы при этом расходуются крайне неэффективно.&lt;br /&gt;
&lt;br /&gt;
При измерениях использовалась реализация алгоритма, имеющаяся в библиотеке Intel® MKL, расчёты проводились на суперкомпьютере «Ломоносов». Код доступен [https://bitbucket.org/timokhin_ivan/fft/src здесь].&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и особенности реализации алгоритма ===&lt;br /&gt;
Как уже отмечалось выше, алгоритм существенно нелокален и плохо поддаётся распараллеливанию, а также обладает достаточно сложной структурой (почти весь состоит из рекурсивных вызовов).  В связи с этими обстоятельствами, рассчитывать на эффективное использование системных ресурсов не приходится.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
Как уже было указано, эффективность параллельной реализации крайне низкая, что связано с заложенными с саму структуру алгоритма глобальными пересылками, так что существенно повлиять на эту ситуацию без кардинальных изменений в алгоритме не представляется возможным. При работе на системе с распределённой памятью лучше всего по возможности избегать параллельного преобразования Фурье вовсе, используя по возможности параллелизм в других частях задачи.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм быстрого преобразования Фурье относится к базовым алгоритмам вычислительной математики, а потому его реализации существуют во многих библиотеках.  Перечислим некоторые из них:&lt;br /&gt;
&lt;br /&gt;
* [//software.intel.com/en-us/intel-mkl Intel® Math Kernel Library] — содержит в том числе и версию для распределённых систем.  Имеет интерфейсы для C, C++ и Fortran.&lt;br /&gt;
* [http://www.fftw.org FFTW] — содержит распределённую реализацию начиная с версии 3.3.  Имеет интерфейсы для C и Fortran.&lt;br /&gt;
* [//www.gnu.org/software/gsl GNU Scientific Library] — имеет интерфейс для C.&lt;br /&gt;
* [//developer.nvidia.com/cufft cuFFT] — реализация алгоритма для GPU от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft clFFT] — реализация алгоритма на OpenCL для CPU и GPU.  Поддерживает только &amp;lt;math&amp;gt;N = 2^p 3^q 5^r 7^s&amp;lt;/math&amp;gt;&lt;br /&gt;
* [http://www.ibm.com/systems/power/software/essl IBM® Engineering and Scientific Subroutine Library] — для процессоров IBM® POWER™, имеет интерфейсы для C, C++ и Fortran, существует версия для многопроцессорных систем.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>IgorS</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=20034</id>
		<title>Участник:Timokhinivan/Быстрое преобразование Фурье</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=20034"/>
		<updated>2016-12-05T07:21:38Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразование Фурье&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Автор описания: [[U:Timokhinivan|Тимохин И.В.]], ВМК МГУ.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
В общем случае многомерное преобразование Фурье соответствует умножению на матрицу преобразования по каждой из координат, что для массива данных &amp;lt;math&amp;gt;N_1\times N_2 \times \cdots \times N_m&amp;lt;/math&amp;gt; означает выполнение &amp;lt;math&amp;gt;\sum_{i=1}^m \prod_{k\neq i} N_k&amp;lt;/math&amp;gt; одномерных преобразований. Ключевым наблюдением, приводящим к быстрому алгоримту для вычисления преобразования, является возможность свести (с некоторыми оговорками) одномерное преобразование к двумерному, которое для &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; требует &amp;lt;math&amp;gt;m + n&amp;lt;/math&amp;gt; рекурсивных вызовов, что, при применении такого метода на всех уровнях рекурсии, даёт общую сложность алгоритма &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Преобразование Фурье задаётся следующей формулой:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y_k = \sum_{l = 0}^{N-1} X_l \epsilon^{kl}_N,\qquad k = \overline{0,N-1}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;\epsilon_N = e^{-\frac{2i\pi}{N}}&amp;lt;/math&amp;gt; — первообразный корень&lt;br /&gt;
из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (т.е. &amp;lt;math&amp;gt;\epsilon_N&amp;lt;/math&amp;gt; является корнем из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, но не является корнем никакой меньшей степени). Число &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; при этом называется порядком преобразования.&lt;br /&gt;
&lt;br /&gt;
Таким образом, преобразование Фурье является линейным и задаётся&lt;br /&gt;
матрицей &amp;lt;math&amp;gt;F = \left\{ \epsilon^{kl}_N \right\}_{k,l = 0}^{N-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Многомерное преобразование Фурье ====&lt;br /&gt;
Естественным обобщением описанного одномерного преобразования Фурье является многомерный вариант:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y(l_1,\ldots,l_d) = \sum_{k_1 = 0}^{N_1-1} \cdots \sum_{k_d = 0}^{N_d-1} X(k_1,\ldots,k_d) \prod_{j=1}^{d} \epsilon_{N_j}^{l_j k_j}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Многомерное преобразование Фурье эквивалентно последовательному выполнению одномерных преобразований по каждому измерению; при использовании для этого рекурсивного алгоритма, общая сложность вычислений также составляет &amp;lt;math&amp;gt;O(N\log N)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;N = \prod_{j=1}^d N_j&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Сведение к (почти) двумерному преобразованию (алгоритм Кули-Тьюки) ====&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — составное, т.е. &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt;, то возможно&lt;br /&gt;
существенно сократить вычислительные расходы за счёт сведения к&lt;br /&gt;
двумерному преобразованию Фурье.  А именно, положим &amp;lt;math&amp;gt;X(l_1, l_2)&lt;br /&gt;
= X_{l_1 n + l_2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt; Y(k_1, k_2) = Y_{k_1 m + k_2}&amp;lt;/math&amp;gt;,&lt;br /&gt;
где &amp;lt;math&amp;gt;l_1, k_2 = \overline{0, m-1}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;l_2, k_1 =&lt;br /&gt;
\overline{0, n-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В этом случае&amp;lt;ref name=&amp;quot;bachv&amp;quot;&amp;gt;Н.С. Бахвалов, Н.П. Жидков, Г.М. Кобельков, &#039;&#039;Численные методы&#039;&#039;, 2003 г., ISBN: 5-94774-060-5&amp;lt;/ref&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{align}&lt;br /&gt;
    Y(k_1, k_2) &amp;amp;=&amp;amp; \sum_{l_2=0}^{n-1} (\epsilon^{k_2 l_2}_N \hat{X}(k_2, l_2)) \epsilon^{k_1 l_2}_n \\&lt;br /&gt;
    \hat{X}(k_2, l_2) &amp;amp;=&amp;amp; \sum_{l_1=0}^{m-1} X(l_1, l_2) \epsilon^{k_2 l_1}_m&lt;br /&gt;
  \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это даёт следующий алгоритм вычисления преобразования:&lt;br /&gt;
&lt;br /&gt;
# Записываем исходный вектор в матрицу &amp;lt;math&amp;gt;m\times n&amp;lt;/math&amp;gt; по строкам.&lt;br /&gt;
# Применяем к каждому столбцу преобразование Фурье.&lt;br /&gt;
# Умножаем элемент в позиции &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;\epsilon^{ij}_N&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Применяем к каждой строке преобразование Фурье.&lt;br /&gt;
# Результат записан в получившейся матрице по столбцам.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм уже даёт существенный выигрыш по сравнению с обычным&lt;br /&gt;
умножением матрицы на вектор: &amp;lt;math&amp;gt; O(m^2 n + n^2 m) &amp;lt;/math&amp;gt; против&lt;br /&gt;
&amp;lt;math&amp;gt;O(m^2 n^2)&amp;lt;/math&amp;gt;.  Однако наилучших результатов можно добиться,&lt;br /&gt;
если применять этот алгоритм рекурсивно на этапах 2 и 4.&lt;br /&gt;
&lt;br /&gt;
Так, если &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;, то целесообразно&lt;br /&gt;
на каждом уровне рекурсии «отщеплять» одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (уровней рекурсии при этом будет &amp;lt;math&amp;gt;K \sim \log N&amp;lt;/math&amp;gt;).  В этом&lt;br /&gt;
случае задача сводится к вычислению &amp;lt;math&amp;gt; \prod_{i\neq j} p_i &amp;lt;/math&amp;gt;&lt;br /&gt;
преобразований Фурье порядка &amp;lt;math&amp;gt;p_j&amp;lt;/math&amp;gt; для всех&lt;br /&gt;
&amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;.  При небольших &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (например, 2), это&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; Y_0 = \sum_{l=0}^{N-1} X_l&amp;lt;/math&amp;gt; вычисляется непосредственно.&lt;br /&gt;
# Любым способом вычисляется &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; — первообразный корень мультипликативной группы поля &amp;lt;math&amp;gt;\mathbb{Z}_{N}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# &amp;lt;math&amp;gt; Y_{g^{N-p} \bmod N} = X_0 + \sum_{q=0}^{N-2} X_{g^q \bmod N} \epsilon_N^{g^{q-p}\bmod N} &amp;lt;/math&amp;gt;.  &lt;br /&gt;
&lt;br /&gt;
Последнее выражение представляет собой свёртку и вычисляется с помощью всё того же преобразования Фурье.  В принципе, это можно проделывать непосредственно, поскольку порядок свёртки &amp;lt;math&amp;gt;N - 1&amp;lt;/math&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;
&amp;lt;math&amp;gt;O(mn (\log m + \log n))&amp;lt;/math&amp;gt; операций против&lt;br /&gt;
&amp;lt;math&amp;gt;O(mn)&amp;lt;/math&amp;gt; для умножения на поправочные коэффициенты (шаг 3).&lt;br /&gt;
&lt;br /&gt;
То же верно и для алгоритма в целом в случае &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;;&lt;br /&gt;
а именно, суммарно наибольшие вычислительные затраты связаны с&lt;br /&gt;
вычислением преобразований Фурье порядка &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;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;n&amp;lt;/math&amp;gt; рекурсивных вызовов алгоритма.&lt;br /&gt;
# Умножение всех элементов рабочего вектора на поправочные коэффициенты.&lt;br /&gt;
# Ещё &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рекурсивных вызовов.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Псевдокод (для метода Кули-Тьюки):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;fft(X, N)&lt;br /&gt;
begin&lt;br /&gt;
  (m, n) = factor(N)&lt;br /&gt;
&lt;br /&gt;
  if (n == 1) // Предполагается, что n &amp;lt;= m&lt;br /&gt;
    direct_fft(X, N) // Через умножение на матрицу&lt;br /&gt;
&lt;br /&gt;
  // 1&lt;br /&gt;
  transpose(X, m, n) //Транспонирует X как матрицу m x n, хранящуюся по строкам&lt;br /&gt;
   &lt;br /&gt;
  // 2&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    fft(X(i * m : (i + 1) * m - 1), m)&lt;br /&gt;
&lt;br /&gt;
  // 3&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    do j = 0, m - 1&lt;br /&gt;
      X(i * m + j) *= eps(i * j, N)&lt;br /&gt;
&lt;br /&gt;
  // 4&lt;br /&gt;
  transpose(X, n, m)&lt;br /&gt;
&lt;br /&gt;
  // 5&lt;br /&gt;
  do j = 0, m - 1&lt;br /&gt;
    fft(X(j * n : (j + 1) * n - 1), n)&lt;br /&gt;
&lt;br /&gt;
  // 6&lt;br /&gt;
  transpose(X, m, n)&lt;br /&gt;
end&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь &amp;lt;code&amp;gt;eps&amp;lt;/code&amp;gt; может как вычислять соответствующие коэффициенты, так и брать их из заранее заготовленной таблицы.  Последний способ требует увеличения затрат по памяти, но позволяет существенно сэкономить на вычислениях, а потому является предпочтительным.&lt;br /&gt;
&lt;br /&gt;
Кроме того, шаги 3 и 4 можно переставлять произвольным образом (с очевидными изменениями в шаге 3).&lt;br /&gt;
&lt;br /&gt;
Особо следует отметить, что если порядок элементов в выходном массиве не важен, шаг 6 можно пропустить.  &lt;br /&gt;
&lt;br /&gt;
Так, например, если преобразование Фурье используется для вычисления свёртки векторов, то конкретный порядок элементов в их образах Фурье совершенно не важен, лишь бы он был одинаков для обоих векторов.  В этом случае в прямом преобразовании можно опустить шаг 6, а в обратном — 1.&lt;br /&gt;
&lt;br /&gt;
Если необходимо учитывать случай большого простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (что может и не требоваться, в зависимости от задачи), то в первом условном операторе следует дополнительно проверить величину &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; и, если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; большое, переключиться на алгоритм Радера.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Непосредственно из описания алгоритма получаем, что если сложность&lt;br /&gt;
вычисления преобразования Фурье обозначить &amp;lt;math&amp;gt;f_N&amp;lt;/math&amp;gt;, то&lt;br /&gt;
&amp;lt;math&amp;gt;f_{mn} = n f_{m} + m f_{n} + mn&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N = \prod_{i=1}^K p_i&amp;lt;/math&amp;gt;, и на каждом шаге&lt;br /&gt;
«отщеплять» от него одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt;, а преобразование Фурье для&lt;br /&gt;
&amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; вычислять «в лоб», то общее количество операций&lt;br /&gt;
составит &amp;lt;math&amp;gt;O\left(N\sum_{i=1}^K p_i\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В частности, если &amp;lt;math&amp;gt;p_i \leq P&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;K \leq \log_P N&amp;lt;/math&amp;gt;&lt;br /&gt;
и для сложности получаем &amp;lt;math&amp;gt;O(NP\log_P N)&amp;lt;/math&amp;gt;.  Полагая&lt;br /&gt;
&amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; фиксированным, получаем заявленную сложность &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
[[Файл:FourierRecursiveGraph.svg|center|thumb|600px|&#039;&#039;&#039;Рисунок 1.&#039;&#039;&#039; Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 15&amp;lt;/math&amp;gt;. Исходные данные обозначены фиолетовым, результат — красным.  Преобразования Фурье для &amp;lt;math&amp;gt;m = 5&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n = 3&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты представлено оранжевыми узлами.]]&lt;br /&gt;
&lt;br /&gt;
Каждый уровень рекурсивного вызова состоит из трёх этапов: рекурсивный вызов по столбцам, умножение на поправочные коэффициенты и рекурсивного вызова по строкам. Таким образом для высоты ЯПФ имеем&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 h(mn) = 1 + h(m) + h(n)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для «элементарных» БПФ высота ЯПФ будет та же, что и для [[Умножение плотной матрицы на вектор|умножения матрицы на вектор]].&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учётом разложения &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;h(N) = O\left(\sum_{i=1}^K p_i\right) = O(P \log_P N) = O(\log N)&amp;lt;/math&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;
&amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n&amp;lt;/math&amp;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;N&amp;lt;/math&amp;gt;.  Однако в частных случаях, возможны более компактные представления для параметров и результатов.&lt;br /&gt;
&lt;br /&gt;
Для описания этих случаев нам понадобится матрица&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Q = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;   \\ &lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp; 1 \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp; 1 &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp; \cdots &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp; 1 &amp;amp;        &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp; 1 &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — вещественный, то &amp;lt;math&amp;gt;Y = Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — чисто мнимый, то &amp;lt;math&amp;gt;Y = -Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — вещественный.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = -Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — чисто мнимый.&lt;br /&gt;
&lt;br /&gt;
В каждом случае, &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; требуют для своего хранения в два раза меньше памяти, чем в общем случае.&lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии дополнительной информации о входном векторе, возможно сократить объём памяти используемый для хранения входных и выходных данных.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
Обозначая матрицу преобразования &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, имеем&amp;lt;ref name=&amp;quot;voevodtyrt&amp;quot;&amp;gt;В.В. Воеводин, Е.Е. Тыртышников, &#039;&#039;Вычислительные процессы с теплицевыми матрицами&#039;&#039;, Москва, «Наука», 1987 г.&amp;lt;/ref&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;F = F^T&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^{-1} = \tfrac{1}{N} F^*&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^* = QF &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Свойство 2 представляет особый интерес, поскольку позволяет вычислять обратное преобразование Фурье по тем же алгоритмам, что и прямое, поскольку &amp;lt;math&amp;gt;F^*&amp;lt;/math&amp;gt; представляет собой матрицу того же вида, что и &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, но с &amp;lt;math&amp;gt;\bar{\epsilon}&amp;lt;/math&amp;gt; вместо &amp;lt;math&amp;gt;\epsilon&amp;lt;/math&amp;gt;.  В сущности, свойство 3 позволяет свести задачу к исходной непосредственно, но ценой дополнительных пересылок.&lt;br /&gt;
&lt;br /&gt;
Кроме того, из второго свойства следует&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 \|F x\|_2 = \sqrt{N} \|x\|_2&lt;br /&gt;
&amp;lt;/math&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;\epsilon^{l}_{n} = \epsilon^{lm}_{mn} = \epsilon^{lm}_{N}&amp;lt;/math&amp;gt;, все необходимые рекурсивному вызову значения поворотных коэффициентов уже имеются в используемом в текущем вызове массиве, а потому его достаточно подготовить один раз на весь алгоритм.&lt;br /&gt;
&lt;br /&gt;
==== Нерекурсивный случай ====&lt;br /&gt;
&lt;br /&gt;
На нижнем уровне рекурсии преобразование Фурье вычисляется путём умножения на матрицу.  Поскольку этот алгоритм, вообще говоря, более пригоден для векторизации, чем рекурсивная реализация, имеет смысл не разбивать &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; на &#039;&#039;совсем&#039;&#039; мелкие множители (&amp;lt;math&amp;gt;p_i \equiv 2&amp;lt;/math&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;
&#039;&#039;Многие&#039;&#039; используемые в алгоритме данные зависят только от порядка преобразования — массив поворотных коэффициентов, факторизация &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; в алгоритме Радера, конкретная задаваемая им перестановка и оптимальный порядок свёртки там же.  Вычисление некоторых из них сопоставимо по сложности со всем остальным алгоритмом.  Имея это ввиду, если планируется вычисление нескольких преобразований Фурье одного порядка, имеет смысл подготовить все эти данные заранее.&lt;br /&gt;
&lt;br /&gt;
==== Численная устойчивость ====&lt;br /&gt;
&lt;br /&gt;
Поскольку все преобразования в алгоритме являются, с точностью до константы &amp;lt;math&amp;gt;\leq \sqrt{N}&amp;lt;/math&amp;gt;, унитарными, катастрофического роста ошибок округления не предвидится, а потому все вычисления возможно проводить с целевой точностью.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
[[Файл:IT_FFTMemaccess.png|center|thumb|800px|Рисунок 2. Профиль обращений в память простого алгоритма Кули-Тьюки с &amp;lt;math&amp;gt;p = (2, 2, 3, 5, 2)&amp;lt;/math&amp;gt;. Сверху вниз: вспомогательная память для прямого умножения матрицы на вектор, память для хранения поворотных коэффициентов, рабочий вектор. Реализация не использует транспонирования векторов в явном виде.]]&lt;br /&gt;
&lt;br /&gt;
Как видно, алгоритм в целом обладает невысокой локальностью, что связано с его структурой (множество рекурсивных шагов, каждый из которых охватывает существенную часть рабочего вектора) и организацией доступа к поворотным коэффициентам. Некоторого улучшения локальности можно добиться, если транспонировать рабочую «матрицу» в памяти, но это либо требует дополнительной памяти, либо, если транспонировать «на месте», существенно усложняет алгоритм.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В случае составного &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; с &#039;&#039;достаточно&#039;&#039; большими делителями, наиболее очевидным и простым вариантом является распределение по процессорам рекурсивных вызовов алгоритма Кули-Тьюки. В этом случае на весь алгоритм приходится только три пересылки, но зато они проходят по схеме «все-со-всеми» (транспонирование матрицы) и не могут выполняться параллельно с вычислениями.&lt;br /&gt;
&lt;br /&gt;
Отдельно заметим, что, как уже упоминалось, первой либо последней группы пересылок можно избежать, если порядок элементов результата не важен.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; простое либо один из множителей в &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; всегда мал, ситуация усложняется. В этом случае придётся применять алгоритм Радера на межпроцессорном уровне, что связано с более сложной схемой пересылок и большим их количеством (т.к. приходится осуществлять преобразование Фурье дважды: прямое и обратное). &lt;br /&gt;
&lt;br /&gt;
В связи с этим, некоторые существующие реализации (например, Intel® MKL) просто не поддерживают этот случай. Впрочем, некоторые реализации не поддерживают большие простые делители &#039;&#039;вовсе&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
Хотя, как уже было указано, алгоритм поддаётся реализации с относительно небольшим числом пересылок, следует отметить, что вычислительна нагрузка на отдельные вычислительные ядра невелика и сопоставима с объёмом пересылок.  При этом сами пересылки достаточно объёмны (весь рабочий вектор), нелокальны (все-со-всеми), и не могут проводиться одновременно с вычислениями.  По этим причинам, ожидать от алгоритма хорошей масштабируемости не приходится, несмотря на «хорошие» свойства информационного графа.  Это и подтверждается экспериментом:&lt;br /&gt;
&lt;br /&gt;
[[Файл:TIFFTEfficiency.png|center|thumb|800px|Рисунок 3. Эффективность распараллеливания быстрого преобразования Фурье, выраженная как отношение времени работы на одном процессоре к произведению времени работы на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; процессорах на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;.  &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — порядок преобразования.]]&lt;br /&gt;
&lt;br /&gt;
При этом, однако, умеренного ускорения всё же удаётся добиться:&lt;br /&gt;
[[Файл:TIFFTSpeedup.png|center|thumb|800px|Рисунок 4.  Ускорение, получаемое при распараллеливании преобразования Фурье.  Обозначения те же, что и на рис. 3]]&lt;br /&gt;
&lt;br /&gt;
В общем, можно заметить, что, если возникла необходимость вычислять преобразование Фурье параллельно, добавление вычислительных узлов является целесообразным, однако такой ситуации следует по возможности избегать, поскольку ресурсы параллельной системы при этом расходуются крайне неэффективно.&lt;br /&gt;
&lt;br /&gt;
При измерениях использовалась реализация алгоритма, имеющаяся в библиотеке Intel® MKL, расчёты проводились на суперкомпьютере «Ломоносов». Код доступен [https://bitbucket.org/timokhin_ivan/fft/src здесь].&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и особенности реализации алгоритма ===&lt;br /&gt;
Как уже отмечалось выше, алгоритм существенно нелокален и плохо поддаётся распараллеливанию, а также обладает достаточно сложной структурой (почти весь состоит из рекурсивных вызовов).  В связи с этими обстоятельствами, рассчитывать на эффективное использование системных ресурсов не приходится.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
Как уже было указано, эффективность параллельной реализации крайне низкая, что связано с заложенными с саму структуру алгоритма глобальными пересылками, так что существенно повлиять на эту ситуацию без кардинальных изменений в алгоритме не представляется возможным. При работе на системе с распределённой памятью лучше всего по возможности избегать параллельного преобразования Фурье вовсе, используя по возможности параллелизм в других частях задачи.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм быстрого преобразования Фурье относится к базовым алгоритмам вычислительной математики, а потому его реализации существуют во многих библиотеках.  Перечислим некоторые из них:&lt;br /&gt;
&lt;br /&gt;
* [//software.intel.com/en-us/intel-mkl Intel® Math Kernel Library] — содержит в том числе и версию для распределённых систем.  Имеет интерфейсы для C, C++ и Fortran.&lt;br /&gt;
* [http://www.fftw.org FFTW] — содержит распределённую реализацию начиная с версии 3.3.  Имеет интерфейсы для C и Fortran.&lt;br /&gt;
* [//www.gnu.org/software/gsl GNU Scientific Library] — имеет интерфейс для C.&lt;br /&gt;
* [//developer.nvidia.com/cufft cuFFT] — реализация алгоритма для GPU от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft clFFT] — реализация алгоритма на OpenCL для CPU и GPU.  Поддерживает только &amp;lt;math&amp;gt;N = 2^p 3^q 5^r 7^s&amp;lt;/math&amp;gt;&lt;br /&gt;
* [http://www.ibm.com/systems/power/software/essl IBM® Engineering and Scientific Subroutine Library] — для процессоров IBM® POWER™, имеет интерфейсы для C, C++ и Fortran, существует версия для многопроцессорных систем.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=19439</id>
		<title>Обсуждение участника:Timokhinivan</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=19439"/>
		<updated>2016-11-29T08:01:54Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4-2.6 не соответствуют содержанию описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
* не указаны авторы статьи и их вклад--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
** Автор один, я (Тимохин Иван, 603 гр. ВМК МГУ) --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 14:50, 27 октября 2016 (MSK)&lt;br /&gt;
*** Так укажите себя, чтобы не было таких вопросов--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 31 октября 2016 (MSK).&lt;br /&gt;
* в п.п. 2.4 следует добавить исходный код программы и описание компьютерной платформы,  с подробным описанием условий, в которых производились эксперименты.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:37, 18 ноября 2016 (MSK)&lt;br /&gt;
*Подробнее опишите компьютерную платформу и условия, в которых производились эксперименты: (какой компилятор, с какими опциями использовался, какие версии библиотек, на каких узлах проводился запуск и т.д.)--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 11:01, 29 ноября 2016 (MSK)  &lt;br /&gt;
** Исходный код нужен даже если это просто вызов библиотечной функции? --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 23:55, 18 ноября 2016 (MSK)&lt;br /&gt;
*** да.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 12:30, 21 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* В общем описании алгоритма нужны слова именно про алгоритм: следует рассказать идею быстрого преобразования, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не вполне понятны слова &amp;quot;первообразный корень из 1 степени N&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* В последней формуле раздела 1.2 индексы k и l, кажется, должны идти через запятую. Если там рассматривается их произведение, следует уточнить, какие значения принимает каждый из них [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2.3 - понятие &amp;quot;порядок преобразования&amp;quot; нуждается в пояснении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не хватает слов о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=19438</id>
		<title>Обсуждение участника:Timokhinivan</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=19438"/>
		<updated>2016-11-29T08:01:18Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4-2.6 не соответствуют содержанию описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
* не указаны авторы статьи и их вклад--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
** Автор один, я (Тимохин Иван, 603 гр. ВМК МГУ) --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 14:50, 27 октября 2016 (MSK)&lt;br /&gt;
*** Так укажите себя, чтобы не было таких вопросов--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 31 октября 2016 (MSK).&lt;br /&gt;
* в п.п. 2.4 следует добавить исходный код программы и описание компьютерной платформы,  с подробным описанием условий, в которых производились эксперименты.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:37, 18 ноября 2016 (MSK)&lt;br /&gt;
**Подробнее опишите компьютерную платформу и условия, в которых производились эксперименты: (какой компилятор, с какими опциями использовался, какие версии библиотек, на каких узлах проводился запуск и т.д.)--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 11:01, 29 ноября 2016 (MSK)  &lt;br /&gt;
** Исходный код нужен даже если это просто вызов библиотечной функции? --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 23:55, 18 ноября 2016 (MSK)&lt;br /&gt;
*** да.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 12:30, 21 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* В общем описании алгоритма нужны слова именно про алгоритм: следует рассказать идею быстрого преобразования, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не вполне понятны слова &amp;quot;первообразный корень из 1 степени N&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* В последней формуле раздела 1.2 индексы k и l, кажется, должны идти через запятую. Если там рассматривается их произведение, следует уточнить, какие значения принимает каждый из них [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2.3 - понятие &amp;quot;порядок преобразования&amp;quot; нуждается в пояснении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не хватает слов о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Ivanov.kir.m/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=18952</id>
		<title>Участник:Ivanov.kir.m/Быстрое дискретное преобразование Фурье</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:Ivanov.kir.m/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=18952"/>
		<updated>2016-11-22T13:03:18Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Алгоритм одномерного преобразования Фурье для действительных чисел&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O (N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O (\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; действительных чисел&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;\lfloor N/2 \rfloor+1&amp;lt;/math&amp;gt; комплексных чисел&lt;br /&gt;
}}&lt;br /&gt;
Авторы описания [[Участник:Ivanov.kir.m | К.М.Иванов]], [[Участник:Pumpkin | А.C.Сапин]] Вклад каждого участника равномерный, поскольку каждый пункт вычитывался и исправлялся обоими участниками. При сильном приближении можно сказать,что больший вклад [[Участник:Ivanov.kir.m | К.М.Иванов]] внес в заполнение теоретической части, а [[Участник:Pumpkin | А.C.Сапин]] в заполнение практической части. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Быстрое преобразование Фурье (БПФ, FFT)&#039;&#039;&#039; — алгоритм быстрого  вычисления дискретного преобразования Фурье (ДПФ). То есть, алгоритм вычисления за количество действий, меньшее чем &amp;lt;math&amp;gt;O(N^{2})&amp;lt;/math&amp;gt;, требуемых для прямого (по формуле) вычисления ДПФ. Иногда под БПФ понимается один из быстрых алгоритмов, называемый алгоритмом прореживания по частоте/времени или алгоритмом по основанию 2, имеющий сложность &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt;. Cуществует несколько различных алгоритмов для вычисления ДПФ считающимся быстрым преобразование Фурье:&lt;br /&gt;
* Алгоритм Кули-Тьюки [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm]&lt;br /&gt;
* Алгоритм Гуда-Томаса [https://en.wikipedia.org/wiki/Prime-factor_FFT_algorithm]&lt;br /&gt;
* Алгоритм Бруна  [https://en.wikipedia.org/wiki/Bruun%27s_FFT_algorithm]&lt;br /&gt;
* Алгоритм Блюштейна [https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm]&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Быстрое дискретное преобразование Фурье&#039;&#039;&#039; (&#039;&#039;БПФ&#039;&#039;) - популярный в современном мире математический метод преобразования вектора/матрицы комплексных/действительных чисел — &#039;&#039;сигнала&#039;&#039; — в вектор/матрицу комплексных чисел — &#039;&#039;спектр&#039;&#039;. С математической точки зрения преобразование сигнала &amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; (как функции по времени &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt;) в спектр &amp;lt;math&amp;gt;\hat{s}&amp;lt;/math&amp;gt; описывается формулой&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\hat{s}(\omega)=\frac{1}{\sqrt{2\pi}}\int\limits_{-\infty}^{\infty}s(t)e^{-it\omega}\,dt&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В отличие от своего классического аналога, БПФ имеет сложность &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;, а не &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, это достигается благодаря разделению исходного вектора на несколько частей так, чтобы, применив к этим частям БПФ и объединив результаты путем умножения на поворотные множители и дополнительного БПФ, можно было получить преобразование Фурье исходного вектора — принцип «разделяй и властвуй».&lt;br /&gt;
&lt;br /&gt;
Область применения данного метода простирается от обработки аудио информации до собственно спектрального анализа.&lt;br /&gt;
Поскольку в общем случае алгоритм БПФ применим к произвольным векторам чисел, выделяют отдельные подзадачи, для которых можно ввести дополнительную оптимизацию. Одной из таких подзадач является быстрое преобразование Фурье вектора действительных чисел, которому и посвящена данная статья. В качестве тестируемой реализации был выбран алгоритм Кули-Тьюки в библиотеке FFTW, написанной на языке C.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание&#039;&#039;&#039;: Поскольку общие описание алгоритма БПФ весьма сложно, в некоторых пунктах данная статья будет ссылаться на простой случай — алгоритм Кули-Тьюки быстрого преобразование Фурье для векторов &#039;&#039;с размерностью равной степени двойки&#039;&#039;. Отличительной особенностью данного алгоритма является то, что он обходится без использования специфических приемов, использующихся именно для степеней четверки, восьмерки и т.п. Однако благодаря тому, что на вход данному алгоритму подается вектор чисто вещественных чисел, выходной вектор удовлетворяет &#039;&#039;эрмитовой избыточности&#039;&#039; (&#039;&#039;Hermitian redundancy&#039;&#039;) , т.е.  &amp;lt;math&amp;gt;out[i]&amp;lt;/math&amp;gt; является сопряженным с &amp;lt;math&amp;gt;out[n-i]&amp;lt;/math&amp;gt;. Это обстоятельство позволяет достичь роста скорости и снижения затрат памяти примерно в 2 раза по сравнению с &#039;&#039;комплексным&#039;&#039; аналогом алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Входные данные: вектор действительных чисел &amp;lt;math&amp;gt;x = (x_1,x_2,...,x_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выходные данные: вектор комплексных чисел &amp;lt;math&amp;gt;X = (X_1,X_2,...,X_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Дискретное преобразование Фурье задается следующей формулой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;X_k = \sum_{n=0}^{N-1} x_n e^{-\frac{2\pi i}{N} nk}, k = \overline{0,N-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В простейшем случае(для степеней двойки) алгоритм Кули-Тьюки рассчитывает ДПФ для четных &amp;lt;math&amp;gt;(x_{2m}=x_0, x_2, \ldots, x_{N-2})&amp;lt;/math&amp;gt; и нечетных элементов отдельно &amp;lt;math&amp;gt;(x_{2m+1}=x_1, x_3, \ldots, x_{N-1})&amp;lt;/math&amp;gt;. Затем результаты рассчетов объединяются для получения ДПФ всей последовательности: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{matrix} X_k &amp;amp; =&lt;br /&gt;
&amp;amp; \sum \limits_{m=0}^{N/2-1} x_{2m}e^{-\frac{2\pi i}{N} (2m)k}   +   \sum \limits_{m=0}^{N/2-1} x_{2m+1} e^{-\frac{2\pi i}{N} (2m+1)k}&lt;br /&gt;
  \end{matrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, получается рекурсивный алгоритм, работающий по принципу [https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0) разделяй и влавствуй].&lt;br /&gt;
&lt;br /&gt;
=== Рекурсивное описание ===&lt;br /&gt;
Алгоритм:&lt;br /&gt;
# Входной вектор &amp;lt;math&amp;gt;a = (a_0,a_2,...,a_{N-1})&amp;lt;/math&amp;gt; преобразуется в матрицу &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; размера &amp;lt;math&amp;gt;n_1 \times n_2 &amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;N=n_1 \cdot n_2&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_1 &amp;lt; n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; A = \begin{pmatrix}&lt;br /&gt;
  a_0 &amp;amp; a_1 &amp;amp; \cdots &amp;amp; a_{n_1-1} \\&lt;br /&gt;
  a_{n_1} &amp;amp; a_{n_1+1} &amp;amp; \cdots &amp;amp; a_{2n_1-1} \\&lt;br /&gt;
  \vdots  &amp;amp; \vdots  &amp;amp; \ddots &amp;amp; \vdots  \\&lt;br /&gt;
  a_{(n_2-1)\cdot n_1} &amp;amp; a_{(n_2-1)\cdot n_1+1} &amp;amp; \cdots &amp;amp; a_{n_2\cdot n_1-1} &lt;br /&gt;
 \end{pmatrix} &amp;lt;/math&amp;gt;&lt;br /&gt;
# К каждой строке полученной матрицы применяется быстрое дискретное преобразование Фурье (БПФ) порядка &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Каждый элемент полученный после применения БПФ умножается на &#039;&#039;поворотные множители&#039;&#039; &amp;lt;math&amp;gt;exp (2 \pi i(m-1)(j-1)/N)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; - номер строки, а &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; - номер столбца&lt;br /&gt;
# Полученная после шагов 1-2 матрица &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; транспонируется&lt;br /&gt;
# К каждой строке матрицы &amp;lt;math&amp;gt;A^T&amp;lt;/math&amp;gt; применяется БПФ порядка &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как алгоритм реализует принцип «разделяй и властвуй», глубина рекурсии составляет &amp;lt;math&amp;gt;O (\log N)&amp;lt;/math&amp;gt; от числа входных элементов.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; Как правило все &#039;&#039;поворотные множители&#039;&#039; вычисляются заранее и хранятся в специальном массиве.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; В общем случае &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; разбивается на &amp;lt;math&amp;gt;n_1 \cdot n_2&amp;lt;/math&amp;gt; так, чтобы &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; сами не являлись простыми числами и разлагались на множители, не являющиеся простыми числами.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; В случае простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; оно дополняется(как правило единицей) до составного числа. В процессе слияния добавленные значения отбрасываются.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В случае размерности входа равной степени двойки, вычислительным ядром алгоритма является так называемая, &amp;quot;&#039;&#039;бабочка&#039;&#039;&amp;quot;. В простейшем случае &amp;quot;&#039;&#039;бабочка&#039;&#039;&amp;quot; представляет из себя двухточечное преобразование. Рассмотрим этот случай:&lt;br /&gt;
&lt;br /&gt;
На вход алгоритму подается двухэлементный вектор ‒ &amp;lt;math&amp;gt; v = (v[0], v[1]) &amp;lt;/math&amp;gt;. Тогда для вычисления будут происходить по следующим формулам:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[0] = W_2^0 v[0] + W_2^0 v[1] = v[0] + W_2^0 v[1] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[1] = W_2^0 v[0] + W_2^1 v[1] = v[0] + W_2^1 v[1] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Данный процесс удобно изобразить с помощью следующей схемы:&lt;br /&gt;
&lt;br /&gt;
[[file:Fftbutterfly.png | Рис. 1. &amp;quot;&#039;&#039;Бабочка&#039;&#039;&amp;quot; для двумерного входа ]]&lt;br /&gt;
&lt;br /&gt;
Для 4-х элеметного вектора &amp;lt;math&amp;gt;v=(v[0],v[1],v[2],v[3])&amp;lt;/math&amp;gt;, алгоритм строится похожим образом. Сначала создаются простейшие &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot;, а потом их результаты соединяются с противоположеной &amp;quot;бабочкой&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[0]=v[0]+W_2^0 v[2]+W_4^0(v[1]+W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[1]=v[0]-W_2^0 v[2]+W_4^1(v[1]-W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[2]=v[0]+W_2^0 v[2]-W_4^0(v[1]+W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[3]=v[0]-W_2^0 v[2]-W_4^1(v[1]-W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Схема в таком случае будет выглядеть следующим образом:&lt;br /&gt;
&lt;br /&gt;
[[file:Fftbutterfly4.png| Рис. 2. &amp;quot;&#039;&#039;Бабочка&#039;&#039;&amp;quot; для четырёхмерного входа ]]&lt;br /&gt;
&lt;br /&gt;
Для случая, когда вход не является степенью двойки, &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot; будут &amp;quot;&#039;&#039;несимментричными&#039;&#039;&amp;quot;, но в остальном вычисления будут проходить схожим образом.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Для исходного вектора &amp;lt;math&amp;gt;a = (a_1,a_2,...,a_N)&amp;lt;/math&amp;gt; размерности &amp;lt;math&amp;gt;N = n_1 \cdot n_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n_1 &amp;lt; n_2&amp;lt;/math&amp;gt; БПФ представляется как:&lt;br /&gt;
#&amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; БПФ порядка &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; &lt;br /&gt;
#&amp;lt;math&amp;gt;n_1 \cdot n_2&amp;lt;/math&amp;gt; умножение комплексных чисел &lt;br /&gt;
#&amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; БПФ порядка &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Схема организации состоит в том, что на каждом шаге &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; для выполнения &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot; все элементы разбиваются на &amp;lt;math&amp;gt;n_{i_1}&amp;lt;/math&amp;gt; векторов по &amp;lt;math&amp;gt;n_{i_2}&amp;lt;/math&amp;gt; элементов, причем &amp;lt;math&amp;gt;n_{i_1} \cdot n_{i_2} = n_i&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;n_i&amp;lt;/math&amp;gt; длина входного вектора на текущем шаге &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;. В случае если такое разбиение невозможно, по причине того что &amp;lt;math&amp;gt;n_i&amp;lt;/math&amp;gt; простое число, исходный вектор на текущем шаге дополняется элементом. В зависимости от номера шага, разница координат для каждой пары элементов увеличивается соразмерно разбиению  &amp;lt;math&amp;gt;(n_{i_1},n_{i_2})&amp;lt;/math&amp;gt;.&lt;br /&gt;
При этом результат суммы записывается в элемент с меньшим номером, а результат вычитания с последующим умножением - в элемент с большим.&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Быстрое дискретное преобразование Фурье выполнимо за &amp;lt;math&amp;gt;O(N(n_1+\cdots+n_m))&amp;lt;/math&amp;gt; действий при &amp;lt;math&amp;gt;N=n_1n_2\cdots n_m&amp;lt;/math&amp;gt; (в простом случае, при &amp;lt;math&amp;gt;N=2^m&amp;lt;/math&amp;gt; необходимо &amp;lt;math&amp;gt;O(N\log_2(N))&amp;lt;/math&amp;gt; действий). Дискретное преобразование Фурье преобразует вектор &amp;lt;math&amp;gt;a = (a_0, \dots, a_{N-1})&amp;lt;/math&amp;gt; в вектор комплексных чисел &amp;lt;math&amp;gt;b = (b_0, \dots, b_{N-1})&amp;lt;/math&amp;gt;, такой, что &amp;lt;math&amp;gt;b_i=\sum_{j=0}^{N-1}a_j\varepsilon^{ij}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\varepsilon^n=1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\varepsilon^k\neq 1&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;0&amp;lt;k&amp;lt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной шаг алгоритма состоит в сведении задачи для &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; чисел к задаче для &amp;lt;math&amp;gt;n_1=N/n_2&amp;lt;/math&amp;gt; числам, где &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; — делитель &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть мы уже умеем решать задачу для &amp;lt;math&amp;gt;N/n_2&amp;lt;/math&amp;gt; чисел. Применим преобразование Фурье к векторам &amp;lt;math&amp;gt;a_i,a_{n_2+i}, \dots, a_{n_2(n_1-1)+i}&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;i=0,1,\dots,n_2-1&amp;lt;/math&amp;gt;. Покажем теперь, что за &amp;lt;math&amp;gt;O(Np)&amp;lt;/math&amp;gt; действий можно решить исходную задачу. Заметим, что &amp;lt;math&amp;gt;b_i=\sum_{j=0}^{n_2-1} \varepsilon^{ij} \left(\sum_{k=0}^{n_1-1}a_{kn_2+j}\varepsilon^{kin_2}\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Выражения в скобках нам уже известны — это &amp;lt;math&amp;gt;(i\mod p)&amp;lt;/math&amp;gt;-тое число после преобразования Фурье &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;-го вектора. Таким образом, для вычисления каждого &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; нужно &amp;lt;math&amp;gt;O(n_2)&amp;lt;/math&amp;gt; действий, а для вычисления всех &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; всего &amp;lt;math&amp;gt;O(Nn_2)&amp;lt;/math&amp;gt; действий, что и требовалось.&lt;br /&gt;
&lt;br /&gt;
В общем случае:&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;4N&amp;gt;2^k\ge2N&amp;lt;/math&amp;gt;. Заметим, что тогда &amp;lt;math&amp;gt;b_i=\varepsilon^{-i^2/2}\sum_{j=0}^{N-1}\varepsilon^{(i+j)^2/2}\varepsilon^{-j^2/2}a_j&amp;lt;/math&amp;gt;. Обозначим &amp;lt;math&amp;gt;\bar{a}_i=\varepsilon^{-i^2/2}a_i&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;\bar{b}_i=\varepsilon^{i^2/2}b_i&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;c_i=\varepsilon^{(2N-2-i)^2/2}&amp;lt;/math&amp;gt;. Тогда &amp;lt;math&amp;gt;\bar{b}_i=\sum_{j=0}^{2N-2-i}\bar{a}_jc_{2N-2-i-j}&amp;lt;/math&amp;gt;, если положить &amp;lt;math&amp;gt;\bar{a}_i=0&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;i\ge N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом задача сведена к вычислению &#039;&#039;свёртки&#039;&#039;, а это можно сделать с помощью трёх преобразований Фурье для &amp;lt;math&amp;gt;2^k&amp;lt;/math&amp;gt; элементов. Выполняем прямое преобразование Фурье для &amp;lt;math&amp;gt;(\bar{a_0}, \cdots, \bar{a}_{2^k-1})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(c_1,\cdots,c_{2^k-1})&amp;lt;/math&amp;gt;, перемножаем поэлементно результаты и выполняем обратное преобразование Фурье.&lt;br /&gt;
&lt;br /&gt;
Вычисления всех &amp;lt;math&amp;gt;\bar{a}_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_i&amp;lt;/math&amp;gt; требуют &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий, три преобразования Фурье требуют &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt; действий, перемножение результатов преобразований Фурье требует &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий, вычисление всех &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; зная значения свертки требует &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий. Итого для дискретного преобразования Фурье требуется &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt; действий для любого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[Файл:InfoGraph Ivanov Sapin.png |center|thumb|800px| Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 21&amp;lt;/math&amp;gt;. Исходные данные обозначены зеленым, результат — синим.  Преобразования Фурье для &amp;lt;math&amp;gt;n_1 = 7&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2 = 3 \text{   } (N = n_1 \cdot n_2)&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты (множители) представлено коричневыми ромбами.]]&lt;br /&gt;
Как видно из рисунка, размер этого графа линейно-логарифмический, а формулы дуг имеют экспоненциальные компоненты.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Если считать только главные члены выражений, то простой алгоритм Кули-Тьюки имеет критический путь, состоящий из &amp;lt;math&amp;gt;log(N)&amp;lt;/math&amp;gt; операций комплексного сложения/вычитания и &amp;lt;math&amp;gt;log(N)&amp;lt;/math&amp;gt; операций комплексного умножения (основание &amp;lt;math&amp;gt;log&amp;lt;/math&amp;gt; целиком зависит от выбранного на каждом шаге алгоритма разбиения). Здесь стоит отметить, что все рекурсивные вызовы БПФ на каждом шаге выполняются независимо, это приводит к тому, что их можно распределить по доступным вычислительным узлам. Аналогичная ситуация обстоит и с умножением на поворотные множетели, что позволяет независимо присоединять его к любому шагу. Таким образом алгоритм БПФ имеет высокий ресурс параллелизма, а алгоритм БПФ в общем случае может быть отнесён к &#039;&#039;логарифмическому&#039;&#039; классу по параллельной сложности. &lt;br /&gt;
&lt;br /&gt;
Кроме того, параллелизм БПФ можно увеличить, если брать &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; по возможности близкими к кратным доступному количеству вычислительных узлов (а не брать просто один из множителей близким к нулю, как в традиционной реализации), это позволит равномерно распределить работу между вычислительными узлами, что сильно повысит эффективность алгоритма благодаря лишь одной внутренней пересылке данных.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; вектор действительных/комплексных чисел &amp;lt;math&amp;gt;a = (a_1,a_2,...,a_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; вектор комплексных чисел &amp;lt;math&amp;gt;b = (b_1,b_2,...,b_{\lfloor N/2 \rfloor+1})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Есть несколько ситуаций, при которых количество потребляемой памяти для хранения входного вектора &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и выходного вектора &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; тможет быть снижено вдвое: &lt;br /&gt;
# Если &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; — вектор действительных чисел, тогда &amp;lt;math&amp;gt;b = E\bar{b}&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; — вектор чисто-мнимых чисел, тогда &amp;lt;math&amp;gt;b = -E\bar{b}&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a = E\bar{a}&amp;lt;/math&amp;gt;, тогда &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; — вектор действительных чисел;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a = -E\bar{b}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; — вектор чисто-мнимых чисел.&lt;br /&gt;
&lt;br /&gt;
Где матрица &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  E = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0 \\ &lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 1 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 1 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \ddots &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 1 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 1 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Таким образом в общем у алгоритма БПФ в случае неограниченных ресурсов:&lt;br /&gt;
*последовательная сложность - &#039;&#039;линейно-логарифмическая&#039;&#039;&lt;br /&gt;
*параллельная сложность  - &#039;&#039;логарифмическая&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
При этом алгоритм полностью детерминирован.&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
В качестве тестируемой реализации была выбрана библиотека FFTW, а именно функция, выполняющая БПФ одномерного вектора действительных чисел.&lt;br /&gt;
==  Особенности последовательной реализации алгоритма ==&lt;br /&gt;
Простейший вариант алгоритма для степеней двойки на языке C:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
#include &amp;lt;complex.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
typedef double complex cplx;&lt;br /&gt;
 &lt;br /&gt;
void _fft(cplx *buf, cplx *out, int n, int step)&lt;br /&gt;
{&lt;br /&gt;
    if (step &amp;lt; n) {&lt;br /&gt;
	_fft(out, buf, n, step * 2); //рекурсивный рассчет&lt;br /&gt;
	_fft(out + step, buf + step, n, step * 2);&lt;br /&gt;
 &lt;br /&gt;
	for (int i = 0; i &amp;lt; n; i += 2 * step) {&lt;br /&gt;
	    cplx t = cexp(-I * PI * i / n) * out[i + step];&lt;br /&gt;
	    buf[i / 2]     = out[i] + t;&lt;br /&gt;
	    buf[(i + n)/2] = out[i] - t;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
void fft(cplx *buf, int n)&lt;br /&gt;
{&lt;br /&gt;
    cplx out[n];&lt;br /&gt;
    for (int i = 0; i &amp;lt; n; i++) out[i] = buf[i];&lt;br /&gt;
 &lt;br /&gt;
    _fft(buf, out, n, 1);&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;
Тестирование проводилось на вычислительном комплексе IBM Blue Gene/P&lt;br /&gt;
&lt;br /&gt;
=== Описание платформы IBM Blue Gene/P ===&lt;br /&gt;
IBM Blue Gene/P — массивно-параллельная вычислительная система, которая состоит из двух стоек, включающих 8192 процессорных ядер (2 x 1024 четырехъядерных вычислительных узлов), с пиковой производительностью 27,9 терафлопс (27,8528 триллионов операций с плавающей точкой в секунду).&lt;br /&gt;
&lt;br /&gt;
Характеристики системы:&lt;br /&gt;
&lt;br /&gt;
* две стойки с вычислительными узлами и узлами ввода-вывода&lt;br /&gt;
* 1024 четырехъядерных вычислительных узла в каждой из стоек&lt;br /&gt;
* 16 узлов ввода-вывода в стойке (в текущей конфигурации активны 8, т.е. одна I/O-карта на 128 вычислительных узлов)&lt;br /&gt;
* выделенные коммуникационные сети для межпроцессорных обменов и глобальных операций&lt;br /&gt;
* программирование с использованием MPI, OpenMP/pthreads, POSIX I/O&lt;br /&gt;
* высокая энергоэффективность: ∼ 372 MFlops/W (см. список Green500)&lt;br /&gt;
* система воздушного охлаждения&lt;br /&gt;
&lt;br /&gt;
Стойка (rack, cabinet) состоит из двух midplane&#039;ов. В midplane входит 16 node-карт (compute node card), на каждой из которых установлено 32 вычислительных узла (compute card). Midplane, 8 x 8 x 8 = 512 вычислительных узлов, — минимальный раздел, на котором становится доступна топология трехмерного тора; для разделов меньших размеров используется топология трехмерной решетки. Node-карта может содержать до двух узлов ввода-вывода (I/O card). Вычислительный узел включает в себя четырехъядерный процессор, 2 ГБ общей памяти и сетевые интерфейсы.&lt;br /&gt;
&lt;br /&gt;
Микропроцессорное ядро:&lt;br /&gt;
&lt;br /&gt;
* модель: PowerPC 450&lt;br /&gt;
* рабочая частота: 850 MHz&lt;br /&gt;
* адресация: 32-битная&lt;br /&gt;
* кэш инструкций 1-го уровня (L1 instruction): 32 KB&lt;br /&gt;
* кэш данных 1-го уровня (L1 data): 32 KB&lt;br /&gt;
* кэш предвыборки (L2 prefetch): 14 потоков предварительной выборки (stream prefetching): 14 x 256 байтов&lt;br /&gt;
* два блока 64-битной арифметики с плавающей точкой (Floating Point Unit, FPU), каждый из которых может выдавать за один такт результат совмещенной операции умножения-сложения (Fused Multiply-Add, FMA)&lt;br /&gt;
* пиковая производительность: 2 FPU x 2 FMA x 850 MHz = 3,4 GFlop/sec per core&lt;br /&gt;
&lt;br /&gt;
Вычислительные узлы и I/O-карты в аппаратном смысле неразличимы и являются взаимозаменяемыми, разница между ними состоит лишь в способе их использования. У них нет локальной файловой системы, поэтому все операции ввода-вывода перенаправляются внешним устройствам.&lt;br /&gt;
&lt;br /&gt;
Вычислительной узел:&lt;br /&gt;
&lt;br /&gt;
* четыре микропроцессорных ядра PowerPC 450 (4-way SMP)&lt;br /&gt;
* пиковая производительность: 4 cores x 3,4 GFlop/sec per core = 13,6 GFlop/sec&lt;br /&gt;
* пропускная способность памяти: 13,6 GB/sec&lt;br /&gt;
* 2 ГБ общей памяти&lt;br /&gt;
* 2 x 4 МБ кэш-памяти 2-го уровня (в документации по BG/P носит название L3)&lt;br /&gt;
* легковесное ядро (compute node kernel, CNK), представляющее собой Linux-подобную операционную систему, поддерживающую значительное подмножество Linux-совместимых системных вызовов&lt;br /&gt;
* асинхронные операции межпроцессорных обменов (выполняются параллельно с вычислениями)&lt;br /&gt;
* операции ввода-вывода перенаправляются I/O-картам через сеть коллективных операций&lt;br /&gt;
&lt;br /&gt;
=== Оценка масштабируемости алгоритма ===&lt;br /&gt;
Масштабируемость алгоритма проверялась с помощью тестовой программы расположенной по [https://github.com/alesapin/parallel_fftw ссылке]. Для эксперимента использовались:&lt;br /&gt;
* Компилятор mpicc&lt;br /&gt;
* Библиотека OpenMPI версии 1.68&lt;br /&gt;
* Библиотека FFTW 3.3.5&lt;br /&gt;
Алгоритм тестировался на входных векторах размера &amp;lt;math&amp;gt;2^{15}&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;2^{26}&amp;lt;/math&amp;gt;. При этом использовались от 8 до 128 процессоров с шагом степени двойки.&lt;br /&gt;
&lt;br /&gt;
[[File:Fftw time good cr.png  |center|thumb|800px| Замеры времени работы параллельной реализации FFTW для входного вектора действительных чисел. По оси Data Size указано &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; -- число входных элементов. По оси Procs Num указано &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; -- число задействованных процессоров. По оси Elapsed Time -- время затраченное на выполненине алгоритма в секундах.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Fftw efficency good cr.png |center|thumb|800px| Эффективность параллельной реализации FFTW для входного вектора действительных чисел. По оси Data Size указано &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; -- число входных элементов. По оси Procs Num указано &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; -- число задействованных процессоров. По оси Effeciency -- эффективность алгоритма в процентах.]]&lt;br /&gt;
&lt;br /&gt;
Из графиков можно сделать следующие выводы:&lt;br /&gt;
* Значительное увеличение числа процессоров не дает значимого уменьшения времени исполнения, что отражено и на графике эффективности. Это означает, что время, в основном, расходуется на пересылки данных, а не на рассчет.&lt;br /&gt;
* При большом количестве элементов и маленьком числе процессоров наблюдается сверхлинейное ускорение, что связано с маленьким число пересылок и активным использованием кеша.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Наиболее известной библиотекой для выполнения различных вариантов быстрого преобразования Фурье является [http://www.fftw.org/ FFTW], разработанная Матео Фриго и Стивеном Джонсоном в Массачусетском технологическом институте. Для преобразования небольших объемов данных используется алгоритм [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Кули - Тьюки], в случае входа больших размеров используется алгоритм [https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Радера] или [https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Блюштейна]. Библиотека считается самой быстрой реализацией БПФ и на входах любых размеров и размерностей сохраняет ассимптотическую сложность &amp;lt;math&amp;gt;O(N\cdot log(N))&amp;lt;/math&amp;gt;. Также, существует версия FFTW с поддержкой [http://www.fftw.org/doc/Distributed_002dmemory-FFTW-with-MPI.html#Distributed_002dmemory-FFTW-with-MPI MPI], которая позволяет с объемами данных не умещающимися в память.&lt;br /&gt;
&lt;br /&gt;
FFTW изначально написана на языке C, однако имеет API для нескольких языков, в том числе Fortran и Ada. Открытая версия библиотеки распространяется под лицензией [https://en.wikipedia.org/wiki/GNU_General_Public_License GNU General Public License]. Также существует реализация для коммерческого использования распространяемая под лицензией [https://en.wikipedia.org/wiki/MIT MIT].&lt;br /&gt;
&lt;br /&gt;
Существует несколько реализаций алгоритма, для рассчета на GPU:&lt;br /&gt;
* [https://developer.nvidia.com/cufft cuFFT] для рассчета на графических ускорителях от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft/ clFFT] для рассчета на GPU и CPU.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Википедия [Электронный ресурс]. Тема: Быстрое преобразование Фурье – Электрон. дан. – URL [https://ru.wikipedia.org/wiki/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5 Быстрое преобразование Фурье] (дата обращения 17.09.2016)&lt;br /&gt;
&lt;br /&gt;
[2] Бахвалов Н. С.,  Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.&lt;br /&gt;
[[en:Fast Discrete Fourier Transform]]&lt;br /&gt;
&lt;br /&gt;
[3] FFTW [Электронный ресурс]. Тема: One-Dimensional DFTs of Real Data – Электрон. дан. – URL [http://www.fftw.org/doc/One_002dDimensional-DFTs-of-Real-Data.html#One_002dDimensional-DFTs-of-Real-Data One-Dimensional DFTs of Real Data] (последняя дата обращения 14.19.2016)&lt;br /&gt;
&lt;br /&gt;
[4] FFTW [Электронный ресурс]. – Электрон. дан. – URL [http://www.fftw.org/ FFTW Lib] (последняя дата обращения 14.19.2016)&lt;/div&gt;</summary>
		<author><name>IgorS</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:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9/Zavolskov/Zemlyanskiy&amp;diff=18920</id>
		<title>Участник:Роман Землянский/Zavolskov/Zemlyanskiy</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:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9/Zavolskov/Zemlyanskiy&amp;diff=18920"/>
		<updated>2016-11-22T09:28:32Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Метод «разделяй и властвуй» вычисления собственных значений и векторов симметричной трехдиагональной матрицы&#039;&#039;&#039;&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;
[[#Схема реализации последовательного алгоритма]]&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;
[[#Последовательная сложность алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Ресурс параллелизма алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Входные и выходные данные алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Свойства алгоритма]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
614 группа&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - это наиболее быстрый из существующих методов, если нужны все собственные значения и собственные векторы трехдиагональной матрицы, начиная с порядка n, примерно равного 26. (Точное значение этого порогового порядка зависит от компьютера.) Его численно устойчивая реализация весьма не тривиальна. В самом деле, хотя впервые метод был предложен еще в 1981 г., &amp;quot;правильный&amp;quot; способ его реализации был найден лишь в 1992 г. Этот способ воплощен LAPACK-программами ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В них стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка, большего чем 25. Для матриц меньшего порядка (или если нужны только собственные значения) происходит автоматический переход к QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Предположим, что нам известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt; T_{2} &amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt; T_{i} = Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. В действительности, они будут рекурсивно вычисляться тем же самым алгоритмом. Установим связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Имеем:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} \Lambda_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
  &amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;&lt;br /&gt;
u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v&lt;br /&gt;
  &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;, получим матрицу, состоящую из последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt; и первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет те же собственные значения, что и подобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} \Lambda_{1} &amp;amp; 0 \\ 0 &amp;amp; \Lambda_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
&lt;br /&gt;
Будем предполагать, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по убыванию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, вычислим её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Поскольку &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденна, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - собственное значение. Заметим, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной добавлением матрицы ранга 1. Определитель такой матрицы легко вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;&#039;Лемма 1.&#039;&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Таким образом,&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; есть корни так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа показанного на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Graphics.PNG|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Можно видеть, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для этого графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Поскольку &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Поэтому корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень находится слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который быстро и монотонно сходится к каждому из корней, если начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать, что на практике метод сходится к каждому собственному значению за ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, а для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; требуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;&#039;Лемма 2.&#039;&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = (D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=u + \alpha(D - \alpha I)^{-1}u - u&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
                                                       поскольку &amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=\alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, что и требовалось.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов требуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, формула не обеспечивает численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Снова детали будут обсуждаться позднее в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
=== Дефляция ===&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это не так, вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а потому &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;дефляции&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, поэтому при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; посредством левого умножения &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не требует никаких затрат. Аналогичное упрощение имеет место в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, связанной с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения является вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов путём умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; Данная операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём и говорится в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
Ей предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Ниже описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
[[File:DivideAndConquerTree.png|thumb|center|left|800px|Рис. 2. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
[[File:InfoGraph.jpg|thumb|center|1200px|Рис. 3. Детальное описание одного блока алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Решение векового уравнения ===&lt;br /&gt;
Подробно стоит поговорить о решении векового уравнения, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
 1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 4. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.4 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:GraphNewton.png|thumb|center|800px|Рис. 4. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
 &amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
 &amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
 &amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (1)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (1) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Удовлетворяла условиям&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (2)&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;\equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&#039;&#039;Вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы посредством стратегии &amp;quot;разделяй и властвуй&amp;quot;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Если &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  1. Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Проанализируем сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов при обработке матрицы размера &amp;lt;math&amp;gt; n x n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. Тогда &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматриваются как плотные матрицы и используется стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке равна 1. Таким образом, именно это умножение составляет наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решая это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно гораздо меньше 1, потому что матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, называемого дефляцией.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
В данном разделе представлен информационный граф алгоритма: на рисунке 5 изображена часть алгоритма, в которой происходит деление - &amp;quot;Divide&amp;quot;, а на рисунке 6 описана часть &amp;quot;Conquer&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[[File:Infograph1.jpg|thumb|center|left|800px|Рис. 5. Информационный граф алгоритма &amp;quot;Разделяй и властвуй&amp;quot; - шаг &amp;lt;b&amp;gt;Divide&amp;lt;/b&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
[[File:Infograph2.jpg|thumb|center|1200px|Рис. 6. Информационный граф алгоритма &amp;quot;Разделяй и властвуй&amp;quot; - шаг &amp;lt;b&amp;gt;Conquer&amp;lt;/b&amp;gt;. Qk*k - матрица собственных значений размерности &amp;lt;b&amp;gt;k&amp;lt;/b&amp;gt;. Lk*k - матрица собственных векторов размерности &amp;lt;b&amp;gt;k&amp;lt;/b&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Операции: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - операция Divide разделения матрицы &amp;lt;math&amp;gt; T &amp;lt;/math&amp;gt; на матрицы &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Индексы - размерность &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_{k}&amp;lt;/math&amp;gt; - операция Conquer сбора матрицы &amp;lt;math&amp;gt; D &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; L_{k} &amp;lt;/math&amp;gt; - операция поиска собственных значений &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S^{&#039;} &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Рассмотрев информационный граф алгоритма, можно заметить, что в структуре имеется два параллельных блока - вызов рекурсивных функций &#039;&#039;dc_eig&#039;&#039; для вычисления собственных значений и векторов матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt; - это единственная часть алгоритма, в которой мы прибегаем к параллелизму. &lt;br /&gt;
&lt;br /&gt;
Рекурсивное разделение матрицы ведёт к иерархии вычислительных подблоков с графом зависимости данных, который выглядит, как двоичное дерево. Данная структура является естественным путем разделения вычислений между процессорами.&lt;br /&gt;
&lt;br /&gt;
В вершине дерева все процессы взаимодействуют. В каждой ветви дерева вычисление делится на два вычислительных подблока, внутри каждого из которых процессы параллельно взаимодействуют. В листьях деревьев каждый процесс занимается вычислением независимо.&lt;br /&gt;
&lt;br /&gt;
В случае реализации без параллелизма, функции &#039;&#039;dc_eig&#039;&#039; отрабатывают последовательно - сначала со входными параметрами &amp;lt;math&amp;gt;T_{1}, Q_{1}, \Lambda_{1}&amp;lt;/math&amp;gt;, затем - &amp;lt;math&amp;gt;T_{2}, Q_{2}, \Lambda_{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, благодаря параллелизму высота алгоритма параллельного составляет &amp;lt;math&amp;gt;log_{2}(n)&amp;lt;/math&amp;gt;, когда как при последовательной реализации высота алгоритма равна сумме геометрической прогрессии &amp;lt;math&amp;gt;1+2+4+8+...+2^{n-1} = 2^{n} - 1&amp;lt;/math&amp;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;
&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объем входных данных:&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt; - в силу симметрии, хранить следует только диагональные, поддиагональые и наддиагональные элементы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
На выходе мы получаем вектор собственных значений длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; собственных векторов длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; , соответствующих собственным значениям исходной матрицы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объем выходных данных:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; n + n^{2} = n(n + 1) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Описанный в данной статье алгоритм является самым быстрым алгоритмом, среди существующих: QR / Бисекция и обратная итерация / Разделяй и властвуй.&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма: &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Параллельная сложность алгоритма (в силу использования дефляции): &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;  или в самых редких случаях &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt; (см. [3] стр. 8).&lt;br /&gt;
&lt;br /&gt;
Особенности алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. Использование дефляции ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Использование адаптированного метода Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&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;quot;Ломоносов&amp;quot; Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 8] (1,2,3,4,8);&lt;br /&gt;
*	размер матрицы [4 : 8192].&lt;br /&gt;
&lt;br /&gt;
Для исследования масштабируемости алгоритм был реализован на библиотеке OpenMP 1.5.5 для языка C++(компилятор g++) на cуперкомпьютере Ломоносов на узле compiler. Для данного узла максимально возможное число потоков - 8. В ходе оценки масштабируемости, программа была запущена в 1, 2, 4, 8 потока. &lt;br /&gt;
Параметры компиляции и запуска: &lt;br /&gt;
&lt;br /&gt;
 ssh compiler &lt;br /&gt;
 module add slurm&lt;br /&gt;
 module add openmpi/1.5.5-icc&lt;br /&gt;
 g++ _scratch/test.c++ -fopenmp -std=c++0x -o test &lt;br /&gt;
 sbatch -p test -n1 run ./test&lt;br /&gt;
&lt;br /&gt;
Для управления количеством потоков использовалась операция OpenMP omp_set_num_threads();&lt;br /&gt;
&lt;br /&gt;
[[File:ResultsDiagram.jpg|thumb|center|left|800px|Рис. 7. График времени работы программы в зависимости от числа процессов и размера задачи]]&lt;br /&gt;
&lt;br /&gt;
Масштабируемость рассматриваемой реализации алгоритма &#039;&#039;&#039;&amp;quot;Разделяй и властвуй&amp;quot;&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* При увеличении числа процессов общая эффективность программы увеличивается, т.к. время чтения и подготовки исходной матрицы остаётся неизменным, а время работы алгоритма уменьшается, однако рост незначительный.&lt;br /&gt;
* При увеличении размерности матрицы общая эффективность программы постепенно уменьшается, т.к. время чтения и подготовки исходных данных растёт быстрее, чем время работы исследуемого алгоритма.&lt;br /&gt;
* При одновременном увеличении как числа процессов, так и размерности матрицы общая эффективность программы уменьшается.&lt;br /&gt;
&lt;br /&gt;
Код исследуемой реализации представлен здесь [http://pastebin.com/QpfYZGkK pastebin.com]&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Описываемый метод реализован в пакете LAPACK, описание которого можно найти в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf этой статье] на страницах 7, 19 и 20, которая указана в списке литературы.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Дж. Деммель, «Вычислительная линейная алгебра» //С. 230-235&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.netlib.org/utk/people/JackDongarra/PAPERS/104_1999_a-parallel-divide-and-conquer-algorithm.pdf Francoise Tisseury, Jack Dongarra, A Parallel Divide and Conquer Algorithm for the Symmetric Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[3] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[4] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[5] [http://www.cscamm.umd.edu/tadmor/pub/linear-stability/Gill_Tadmor_SISC90.pdf Doron Grill and Eitan Tadmor AN &amp;lt;math&amp;gt;O(N2)&amp;lt;/math&amp;gt; METHOD FOR COMPUTING THE EIGENSYSTEM OF &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;x&amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; SYMMETRIC TRIDIAGONAL MATRICES BY THE DIVIDE AND CONQUER APPROACH]&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=18708</id>
		<title>Участник:Kanast74/Алгоритм кластеризации, основанный на минимальном покрывающем дереве</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=18708"/>
		<updated>2016-11-21T09:40:05Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
Статью подготовила: [[Участник:kanast74|Дмитриева Анастасия Владимировна]].&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&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;X=\{x_1...x_n\}&amp;lt;/math&amp;gt;, а также функция расстояния между ними &amp;lt;math&amp;gt;r(e)&amp;lt;/math&amp;gt;. Отметим, что метрика, в которой вычисляются расстояния между объектами, выбирается исходя из требований задачи; наиболее распространенные - это Евклидово расстояние, квадрат Евклидова расстояния, манхэттенская метрика, расстояние Чебышева и степенное расстояние. Далее необходимо применить один из алгоритмов кластеризации, которые можно разделить на две категории: иерархические, состоящие в последовательном поиске кластеров из уже найденных кластеров, а также неиерархические, цель которых состоит в минимизации некоторой целевой функции. К последней группе относятся такие алгоритмы, как алгоритм k-средних, алгоритм EM, нечеткие алгоритмы, а также алгоритмы теории графов, к категории которых относится алгоритм, описываемый в данной статье.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим алгоритм кластеризации на k кластеров, основанный на построении минимального остовного дерева (MST) и который состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
# По исходным данным строится взвешенный неориентированный граф: каждый объект представляется в виде вершины графа, а вес ребра, связывающего две вершины, есть расстояние между объектами в выбранной заранее метрике&lt;br /&gt;
# Для полученного графа строится минимальное остовное дерево, то есть дерево, содержащее все вершины графа и имеющее минимальный суммарный вес своих ребер&lt;br /&gt;
# Удаляется k-1 ребро минимального остовного дерева с максимальным весом&lt;br /&gt;
# Получаются k компонент связности, которые интерпретируются как кластеры&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;nowiki&amp;gt;, наиболее подробно изучить который можно, пройдя по ссылке [&amp;lt;/nowiki&amp;gt;[https://algowiki-project.org/ru/Алгоритм_Крускала Алгоритм Крускала]], поэтому в данной статье его описание не приводится.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
С математической точки зрения задачу кластеризации можно рассматривать как минимизацию среднего внутрикластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} 1} \to min &amp;lt;/math&amp;gt; с одной стороны и максимизации межкластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)\neq c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i) \neq c(x_j)} 1} \to max &amp;lt;/math&amp;gt; с другой стороны, где &amp;lt;math&amp;gt;c(x)&amp;lt;/math&amp;gt; - кластер, к которому принадлежит объект &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Применительно к алгоритмам кластеризации, основанным на теории графов, процедура минимизации/максимизации соответствующих расстояний состоит в выборе определенного порогового значения веса ребра &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;, ребра с весом меньше которого предполагаются связующими для вершин, принадлежащих одному классу, а ребра с весом больше которого предполагаются связующими для вершин, принадлежащих разным классам.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром описываемого алгоритма является построение минимального остовного дерева. В случае, если программа предполагается быть распараллеленной, то операция построения MST будет производиться в отдельности в каждом процессе. Как уже было отмечено, в данном исследовании для построения MST использовался алгоритм Крускала, который будет работать наиболее эффективно, если веса ребер исходного графа отсортированы, поэтому сортировка - это еще одна операция, которая должна быть включена в вычислительное ядро, что предполагает выбранная ревлизация алгоритма. Поэтому в дальнейшем мы не будем разделять сортировку и поиск минимального остовного дерева, предполагая, что они объеденены в единый эффективный алгоритм. &lt;br /&gt;
&lt;br /&gt;
Дадим некоторую справку касательно адгоритма поиска MST, фигурирующего в данном исследовании. Алгоритм Крускала — алгоритм построения минимального остовного дерева взвешенного связного неориентированного графа. Алгоритм впервые описан Джозефом Крускалом в 1956 году.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Крускала находит безопасное ребро для добавления в растущий лес путем поиска ребра &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; с минимальным весом среди всех ребер, соединяющих два дерева в лесу. Обозначим два дерева, соединяемые ребром &amp;lt;math&amp;gt;(u, v) &amp;lt;/math&amp;gt;, как &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; — безопасное для &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; ребро. Алгоритм Крускала является жадным, поскольку на каждом шаге он добавляет к лесу ребро с минимально возможным весом.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
# Построение графа путем задания списков ребер. В общем случае, когда задано некоторое множество объектов, для построения графа может быть использован алгоритм, позволяющий рассчитать расстояние от каждого объекта до каждого за &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; число объектов (вообще говоря, данная операция требует &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; - число ребер в графе, но в нашем случае число ребер подразумевается равным &amp;lt;math&amp;gt;E=\frac{D (n(n-1))}{2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - плотность графа). Однако в рамках данного исследования для тестирования алгоритма использовалась такая технология построения графов, когда в качестве веса каждого ребра назначается некоторое произвольное число из заданного промежутка (например, от 1 до 1000), что также требует &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
# Построение минимального остовного дерева на основании полученного ранее графа, для чего может быть использован один из известных и наиболее распространенных алгоритмов (алгоритм Крускала, алгоритм Борувки или алгоритм Прима). Рассмотрим, например, алгоритм Крускала. На начальном этапе имеется граф, состоящий из вершин - объектов исходной выборки и не содержащий ребер, следовательно, каждая из вершин изначально представляет собой компоненту связности и даже дерево. Алгоритм состоит в выборе на каждом шаге ребра с минимальным весом и добавлении его в дерево, если оно соединяет два дерева. Алгоритм будет работать максимально эффективно, если изначально ребра графа, полученного на первом шаге будут отсортированы (например, по возрастанию). &lt;br /&gt;
# Для получения &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров необходимо удалить из получившегося минимального остовного дерева &amp;lt;math&amp;gt;k-1&amp;lt;/math&amp;gt; ребро с максимальным весом. В частности, в случае, если на предыдущем шаге мы пользовались алгоритмом Крускала для построения MST, ребра получившегося дерева отсортированы по возрастанию, значит для удаления указанных выше ребер потребуется лишь &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
# Наконец, необходимо правильно интерпретировать результат работы алгоритма и перечислить вершины, принадлежащие полученным кластерам; на выполнение данного шага может потребоваться различное число операций в зависимости от формата выходных данных программы: например, в данной реализации выходные данные, как будет сказано в дальнейшем, представлены в виде списка ребер, формирующий итоговый минимальный остовный лес, следовательно в общем случае на его обработку потребуется &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
==== Используемые обозначения и структуры данных ====&lt;br /&gt;
&lt;br /&gt;
Обозначим в качестве &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; граф, содержащий &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; вершин и &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; ребер. Для построения минимального остовного дерева для данного графа используются три операции [2], опишем здесь каждую из них:&lt;br /&gt;
&lt;br /&gt;
# операция MakeSet(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) генерирует дерево (множество) для вершины графа &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, являющейся корнем искомого дерева&lt;br /&gt;
# операция Find(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) определяет, которому из деревьев принадлежит интересующая вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Условимся, что данная операция возвращает ту вершину, которая является корнем искомого дерева&lt;br /&gt;
# операция Union(&amp;lt;math&amp;gt;u, v&amp;lt;/math&amp;gt;) объединяет два множества (два дерева, содержащих соответствующие вершины) под одним корнем&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма === &lt;br /&gt;
&lt;br /&gt;
Приведем здесь псевдокод основной части алгоритма - а именно построения локального минимального остовного дерева при помощи алгоритма Крускала.&lt;br /&gt;
&lt;br /&gt;
Kruskal ( G=(V,E) ):    &lt;br /&gt;
&lt;br /&gt;
1. mst = ∅;    &lt;br /&gt;
&lt;br /&gt;
2. сортировать E по возрастанию;&lt;br /&gt;
&lt;br /&gt;
3. для каждой вершины v из множества вершин V:&lt;br /&gt;
&lt;br /&gt;
4. MakeSet(v);&lt;br /&gt;
&lt;br /&gt;
5. для каждого ребра e = (u, v) из множества ребер E:&lt;br /&gt;
&lt;br /&gt;
6.    if Find(u) ≠ Find(v):&lt;br /&gt;
&lt;br /&gt;
7.    mst = mst ∪ { (u,v) }; &lt;br /&gt;
&lt;br /&gt;
8.    Union(u, v); &lt;br /&gt;
&lt;br /&gt;
9. return mst;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Сперва оценим последовательную сложность составных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
0. O(E): Формирование графа&lt;br /&gt;
&lt;br /&gt;
1. O(1): Инициализация пустого множества. &lt;br /&gt;
&lt;br /&gt;
2. O(Elog(E)): сортировка ребер из множества E. &lt;br /&gt;
&lt;br /&gt;
3. O(V): Инициализация одноточечного множества для каждой вершины из V. &lt;br /&gt;
&lt;br /&gt;
4.    ▲ MakeSet за O(1). &lt;br /&gt;
&lt;br /&gt;
5. O(Elog(V)): Операции Union и Find для каждого ребра из E. &lt;br /&gt;
&lt;br /&gt;
6.    ▲ Find за O(1). &lt;br /&gt;
&lt;br /&gt;
7.    ▲ Присоединение элемента за O(1). &lt;br /&gt;
&lt;br /&gt;
8.    ▲ Union за O(log(V)).&lt;br /&gt;
&lt;br /&gt;
9. O(1): Удаление k-1 ребра для получения k компонент связности&lt;br /&gt;
&lt;br /&gt;
10. O(V): Перечисление вершин, входящих в соответствующие кластеры&lt;br /&gt;
&lt;br /&gt;
11. O(1): Return.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учетом того, что, получаем:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T( G = (V, E) ) = O(1) + O(E\log(E)) + O(V) + O(E\log(V)) + O(E) = O(E\log(E)) + O(E\log(V))  = O(E\log(V)) + O(E\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Итак, оценка сложности алгоритма есть &amp;lt;math&amp;gt;T( G = (V, E) ) = O(E\log(V)) + O(\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
[[file:Clustering kruskal.png|thumb|center|1400px|Рисунок 1. Информационный граф алгоритма]]&lt;br /&gt;
&lt;br /&gt;
По структуре информационного графа можно сделать вывод что высота канонической ярусно-параллельной формы [1] есть &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; число процессов.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Описанный алгоритм позволяет произвести распараллеливание, источник которого будет описан в данном пункте. Прежде всего, необходимо отметить, что данный алгоритм допускает преобразование в гибридную параллельную версию, а именно версию с использованием технологий MPI и OpenMP. &lt;br /&gt;
&lt;br /&gt;
Тем не менее, прежде всего следует установить справедливость свойства используемого алгоритма поиска MST, позволяющего произвести его распараллеливание [3]: &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ассоциативность по рёбрам&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;MSF(E)&amp;lt;/math&amp;gt; – минимальный остовный лес графа с рёбрами &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;. Тогда&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        MSF(E_1 \cup E_2 \cup \dots \cup E_k) = MSF(MSF(E_1) \cup MSF(E_2) \cup \dots \cup MSF(E_k)).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В том случае, когда все объекты заранее известны, и между ними можно рассчитать все попарные расстояния, первый шаг - построение графа - позволяет произвести распараллеливание при помощи обеих вышеуказанных технологий, а именно: разделить объекты между процессами при помощи MPI, а внутри каждой из полученных групп будем рассчитывать попарные расстояния параллельно на нескольких ядрах, использовав технологию OpenMP. Однако в данном исследовании кластеризуемые объекты предполагались заранее неизвестными, так как попарные расстояния между объектами задаются случайным образом, а это означает, что произвести распараллеливание этого шага, которое бы дало хотя бы небольшое ускорение (уменьшение времени работы), не получится.&lt;br /&gt;
&lt;br /&gt;
Если все же объекты заранее были известны, можно сохранить полученную на предыдущем этапе группировку их на отдельные процессы и внутри каждого произвести сортировку расстояний между объектами по возрастанию при помощи алгоритма QSort с использованием технологии OpenMP (в противном случае параллельная структура алгоритма поддерживается начиная с этого шага).&lt;br /&gt;
&lt;br /&gt;
Далее внутри каждой группы объектов (каждый процесс в отдельности) строим минимальное остовное дерево, в конце шага деревья объединяются в одно - общее MST для всех объектов, поддерживая при этом отсортированность расстояний по возрастанию.&lt;br /&gt;
&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  &amp;lt;math&amp;gt; \log{p} &amp;lt;/math&amp;gt; шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
==== Способы разделения графа ====&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;G = (V, E)&amp;lt;/math&amp;gt;, являющийся неориентированным, взвешенным, связным и не имеющим кратных ребер. Пусть полученный граф содержит &amp;lt;math&amp;gt; |V|&amp;lt;/math&amp;gt; вершин и  &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; ребер. Предполагая, что каждый объект можно сравнить с каждым, можно считать, что получаемый граф является полным, а значит число ребер может быть оценено следующим образом: &amp;lt;math&amp;gt;|E|=\frac{|V|(|V|-1)}{2} \le \frac{|V|^2}{2}-\frac{|V|}{2} \Rightarrow O(E)=O(V^2)&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Обозначим в виде &amp;lt;math&amp;gt;w(u,v)&amp;lt;/math&amp;gt; вес ребра, соединяющего вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Тогда граф может быть задан в виде списка ребер, а именно списка троек: &amp;lt;math&amp;gt;(u,v,w(u,v))&amp;lt;/math&amp;gt;, что является более удобным представлением графа для реализации многих алгоритмов по сравнению с матрицей смежности. Помимо этого, данный способ помогает сэкономить память. Недостатков указанного подхода является только то, что установить, с какими вершинами связана интересующая вершина, а также вес соответствующих ребер является весьма затруднительно. Однако для решения поставленной задачи нам это и не потребуется.&lt;br /&gt;
&lt;br /&gt;
Формат выходных данных аналогичный&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
==== Вычислительная мощность ====&lt;br /&gt;
Оценим вычислительную мощность алгоритма в том случае, когда для его реализации используется &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов. Предположим, что алгоритм реализован при помощи первой стратегии (см. выше) и применяется к графу &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt;, тогда:&lt;br /&gt;
&lt;br /&gt;
* Ввод данных: &amp;lt;math&amp;gt;\frac{|E|}{p}&amp;lt;/math&amp;gt; - в среднем столько ребер содержит получаемый каждым процессом связный подграф&lt;br /&gt;
* Вывод данных: &amp;lt;math&amp;gt;\frac{|V| - 1}{p}&amp;lt;/math&amp;gt; - каждый из процессов должен вывести посчитанное локальное минимальное дерево, которое содержит указанное количество ребер, так как остальные ребра, изначально входившие в состав подграфа, не будут входит в локальное MST&lt;br /&gt;
* Вычисление: для вычисления локального MST каждому из процессов требуется &amp;lt;math&amp;gt;\frac{|E|}{p} \log{\frac{|V|}{p}}&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
Таким образом, искомое соотношение может быть записано в следующем виде: &amp;lt;math&amp;gt;\frac{\frac{|E|}{p} \log{\frac{|V|}{p}}}{\frac{|E|}{p} + \frac{|V| - 1}{p}} = \frac{|E| \log{\frac{|V|}{p}}}{|E|+|V|-1}&amp;lt;/math&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;
В данном рзеделе будет производиться оценка прироста производительности параллельного алгоритма по сравнению с последовательным. Наиболее простой способ оценить прирост производительности – использовать закон Амдала, который гласит, что прирост может быть оценен следующим образом: &amp;lt;math&amp;gt; S_p \le \frac{1}{f+\frac{1-f}{p}}&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; – часть алгоритма, которая не может быть распараллелена, &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; – число процессов.&lt;br /&gt;
&lt;br /&gt;
В наиболее тривиальном случае (первая стратегия распараллеливания) распараллеливанию не подлежит считывание входных данных и их разделение между процессами, а также объединение локальных MST в одно. Поэтому сложность последовательной части программы может быть оценена следующим образом: &amp;lt;math&amp;gt;2O(V^2)+O(V^2 \log{V})&amp;lt;/math&amp;gt;. Распараллеливанию подлежит инициализация подграфов при помощи операции MakeSet за &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt;, сортировка ребер по возрастанию внутри каждой группы за &amp;lt;math&amp;gt;O(V^2 \log{V})&amp;lt;/math&amp;gt;, а также построение локальных минимальных остовных деревьев за аналогичное время. Поэтому сложность параллельной части алгоритма может быть оценена так:  &amp;lt;math&amp;gt;O(V)+2 O(V^2 \log{V})&amp;lt;/math&amp;gt;. Таким образом, вычислив отношение сложностей частей программы, получаем, что  &amp;lt;math&amp;gt;f=0.44&amp;lt;/math&amp;gt;. Устремим число процессов  &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; к бесконечности и получим, что максимальное ускорение, которое можно получить путем распараллеливания алгоритма есть &amp;lt;math&amp;gt;2.27&amp;lt;/math&amp;gt;. К сожалению, данная оценка не является точной. Для того, чтобы оценить прирост производительности более качественно, произведем анализ временных диаграмм программы.&lt;br /&gt;
&lt;br /&gt;
===== Анализ временных диаграмм =====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;t_d&amp;lt;/math&amp;gt; – время, которое требуется для того, чтобы разделить граф на подграфы, &amp;lt;math&amp;gt;t_p&amp;lt;/math&amp;gt; – суммарное время вычислений, &amp;lt;math&amp;gt;t_b&amp;lt;/math&amp;gt; – время обратной пересылки локально полученных результатов, &amp;lt;math&amp;gt;t_l&amp;lt;/math&amp;gt; – задержка одной пересылки. Причем можно убедиться в том, что &amp;lt;math&amp;gt;t_p &amp;gt; t_d &amp;gt; t_b &amp;gt; t_l&amp;lt;/math&amp;gt;, также известно, что время выполнения последовательного кода &amp;lt;math&amp;gt;t_{serial} = t_p&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:FirstStrategy.PNG|thumb|center|400px|Рисунок 2. Первая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Произведем анализ первой (наивной) стратегии распараллеливания программы. На временной диаграмме жирным обозначен критический путь, время выполнения которого может быть вычислено по формуле (см: [2]):&lt;br /&gt;
&amp;lt;math&amp;gt;t_{paralel} = 4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда максимальное ускорение для одного процесса есть: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{t_p}{4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А в случае n процессов: &amp;lt;math&amp;gt;speedup = \frac{n}{1+\frac{n (n+1) t_l + n t_d + t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:SecondStrategy.PNG|thumb|center|400px|Рисунок 3. Вторая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Теперь проанализируем вторую стратегию, основная задача которой – уменьшить объем коммуникаций между процессами за счет того, что файл с исходными данными теперь доступен всем процессам. Помимо этого введем в рассмотрение новую величину &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; – время, требующееся на инициализацию множеств вершин. Тогда для одного процесса &amp;lt;math&amp;gt;t_{parallel} = t_i + \frac{t_p}{4} + t_l + 3 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а максимальное ускорение в случае n процессов есть: &amp;lt;math&amp;gt;speedup= \frac{t_{serial}}{t_{parallel}} = \frac{4}{1+\frac{4 t_i + 4 t_l + 3 t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:ThirdStrategy.PNG|thumb|center|400px|Рисунок 4. Третья стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Наконец, проанализируем третью стратегию, основная цель которой – оптимизировать процесс слияния локальных минимальных остовных деревьев, а также привлечь все активные процессы (включая главный) к процессу построения локального MST. В этом случае время выполнения параллельной части программы с использованием лишь одного процесса: &amp;lt;math&amp;gt; t_{parallel}=t_i + \frac{t_p}{4} + t_l + 2 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а суммарное ускорение в общем случае: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{n}{1+\frac{n t_i + n t_l + \log_2{n} t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На рисунке 5 приведены графики, отражающие зависимость получаемых возможных ускорений в зависимости от числа задействованных процессов, посчитанные с использованием четырех различных подходов.&lt;br /&gt;
[[Файл:Comparison.PNG|thumb|center|400px|Рисунок 5. Сравнение стратегий распараллеливания]]&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
[[file:Total.png|thumb|center|400px|Рисунок 6. График зависимости времени вычисления от размера задачи и числа процессов (с учетом времени ввода-вывода)]]&lt;br /&gt;
[[file:Wo_io.png|thumb|center|400px|Рисунок 7. График зависимости времени вычисления от размера задачи и числа процессов (без учета времени ввода-вывода)]]&lt;br /&gt;
&lt;br /&gt;
На графиках видно, как с увеличением объема вычислительных ресурсов происходит прирост в производительности. Данный эффект происходит до некоторых пор, после чего накладные расходы на коммуникацию между процессами нивелируют его. При дальнейшем увеличении числа процессов время решения задачи заметно увеличивается, однако, ввиду длинных очередей на суперкомпьютере Ломоносов данные с числом процессов &amp;lt;math&amp;gt; \ge 256&amp;lt;/math&amp;gt; получить не удалось&lt;br /&gt;
&lt;br /&gt;
Исходный код, запускавшийся на суперкомпьютере Ломоносов-1:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Source codes.docx]]&lt;br /&gt;
&lt;br /&gt;
Для компиляции исходных кодов использовался сервер compiler, компилятор mpicc (из модуля openmpi/1.5.5-icc) с опциями -Wall -O3 -fopenmp.&lt;br /&gt;
Запуск задачи производился скриптом следующего вида: sbatch -n $PROCESSES —ntasks-per-node 4 -t $TIME -p regular4 ompi kruscal_mpi_test_data_bin&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма  [3]===&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/kruskal_min_spanning_tree.html kruskal_min_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#merge-local-minimum-spanning-trees merge_local_minimum_spanning_trees]&amp;lt;/code&amp;gt; реализует алгоритм Крускала;&lt;br /&gt;
** функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают [[алгоритм Борувки]] и алгоритм Крускала.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.mst.minimum_spanning_tree.html minimum_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/KruskalMinimumSpanningTree.html KruskalMinimumSpanningTree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. - СПб.: БХВ-Петербург, 2002. - 608 с.&lt;br /&gt;
&lt;br /&gt;
[2] http://www.gpiskas.com/pdf/kruskal_mpi.pdf (дата обращения: 02.10.2016)&lt;br /&gt;
&lt;br /&gt;
[3] https://algowiki-project.org/ru/Алгоритм_Крускала&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=18704</id>
		<title>Участник:Vagabond/Алгоритм быстрого дискретного пребразования Фурье</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=18704"/>
		<updated>2016-11-21T09:36:25Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
&#039;&#039;&#039;Автор&#039;&#039;&#039;: Данилишин А.Р.&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;X=(x_{1},...,x_{n-1})^{T}&amp;lt;/math&amp;gt; - исходный сигнал, который необходимо преобразовать умножением на тензор второго ранга &amp;lt;math&amp;gt;E_{pk}=e^{\frac{-2kpi\pi}{n}} &amp;lt;/math&amp;gt;. Результатом данного преобразования (&amp;lt;math&amp;gt;E*X&amp;lt;/math&amp;gt;) будет вектор &amp;lt;math&amp;gt;Y=(y_{1},...,y_{n-1})^{T}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2kpi\pi}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. Данная формула является классической формулой ДПФ. Известно, что в общем случае умножение матрицы на вектор требует &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; арифметических операций, поэтому возникает вопрос о сокращении данного количества.&lt;br /&gt;
&lt;br /&gt;
Главная идея БПФ состоит в сокращении многочисленных повторов сложения и умножения, которые выполняются в ДПФ (повторяющиеся множители: &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; Например при условии, что  &amp;lt;math&amp;gt;(pk)mod(n)=const&amp;lt;/math&amp;gt;, при всех &amp;lt;math&amp;gt;k=\overline{0,n-1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;p=\overline{0,n-1}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
За счет использования данного факта, происходит значительное сокращение количества вычислительных операций. В данной статье используется алгоритм Cooley – Tukey, который предполагает собой разбиение исходного множества входных сигналов на два равных подмножества. При этом множество входных сигналов должно быть степенью двойки. Далее операция деления множества продолжается для образовавшихся подмножеств, и так далее. В результате над каждым из полученных подмножеств выполняется БПФ. Данные вычисления можно выполнять независимо друг от друга. Этот факт используется для реализации параллельной версии алгоритма. &lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
*Пусть &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\omega _{n}^{kp}&amp;lt;/math&amp;gt;.&lt;br /&gt;
*Разобьем исходный сигнал &amp;lt;math&amp;gt;x(n)&amp;lt;/math&amp;gt; на два подмножества, с четными &amp;lt;math&amp;gt;x_{1}(2n)&amp;lt;/math&amp;gt; и нечетными &amp;lt;math&amp;gt;x_{2}(2n+1)&amp;lt;/math&amp;gt; номерами.&lt;br /&gt;
Тогда получаем: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}\omega _{n}^{kp}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{(2k+1)p}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выпишем два свойства омеги:&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{\frac{n}{2}}=e^{2(\frac{-2i\pi}{n})}=\omega_{n}^{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{n}^{\frac{n}{2}}=e^{\frac{-2ni\pi}{2n}}=e^{-i\pi}=-1&amp;lt;/math&amp;gt;&lt;br /&gt;
Воспользуемся первым: &amp;lt;math&amp;gt;y_{p}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{2kp}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;. Или, если обозначить &amp;lt;math&amp;gt;y_{1}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;y_{2}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, то получим формулу для вычисления координат изображения БПФ при &amp;lt;math&amp;gt;p=\overline{0...n/2}&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;y_(p)=y_{1}(p)+\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заметим, что &amp;lt;math&amp;gt;y(p); y_{1}(p); y_{2}(p)&amp;lt;/math&amp;gt; обладают свойством периодичности, с периодом &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt;. Покажем это для &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt;y_{1}(p+\frac{n}{2})=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{k(p+\frac{n}{2})}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}\omega_{\frac{n}{2}}^{\frac{kn}{2}}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как: &amp;lt;math&amp;gt;\omega_{\frac{n}{2}}^{\frac{kn}{2}}=e^{\frac{-4kni\pi}{2n}}=e^{-2ki\pi}=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Далее необходимо определить &amp;lt;math&amp;gt;y(p)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;p\geq \frac{n}{2}&amp;lt;/math&amp;gt;. Теперь воспользуемся свойством периодичности обнаруженными нами ранее: &amp;lt;math&amp;gt;y(p+\frac{n}{2})=y_{1}(p+\frac{n}{2})+\omega_{n}^{(p+\frac{n}{2})}y_{2}(p+\frac{n}{2})&amp;lt;/math&amp;gt;. Воспользуемся вторым свойством омеги: &amp;lt;math&amp;gt;y(p)=y_{1}(p)-\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;. Все выкладки, приведенные выше, относятся ко второму этапу алгоритма Cooley – Tukey, который был использован в данной статье. Последующие шаги данного этапа делят подмножества полученные на предыдущем шаге вычисления БПФ.&lt;br /&gt;
&lt;br /&gt;
В конечном итоге вышеупомянутый алгоритм сводится к двум этапам:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Бит-реверсирование&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Бит-реверсирование – это преобразование двоичного числа путем изменения порядка следования бит в нем на противоположный. Бит-реверсирование применимо только к индексам элементов массива и предназначено для изменения порядка следования этих элементов, при этом значения самих элементов не изменяются. Так, к примеру, четырехмерный вектор &amp;lt;math&amp;gt;(x_{0},x_{1},x_{2},x_{3})&amp;lt;/math&amp;gt; изменится на &amp;lt;math&amp;gt;(x_{0},x_{2},x_{1},x_{3})&amp;lt;/math&amp;gt;. Данная процедура необходима для разделения исходного сигнала на элементы подмножеств с четными и нечетными номерами, которые необходимы для вычисления &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Вычисление БПФ&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
После выполнения этапа бит-реверсирования выполняются вычисления проиллюстрированные &#039;&#039;на рисунке 1&#039;&#039; в разделе [[#Информационный граф|Информационный граф]]. Рисунок 1 приведен для случая четырехмерного входного сигнала. Он предполагает совершение двух этапов. В общем же количество этапов равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Каждый этап содержит в себе вычисление одной и той же операции &amp;quot;бабочки&amp;quot;, где на вход подаются два числа &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;, на выходе же получаются &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;- это &#039;&#039;коэффициент поворота&#039;&#039;. Общая формула для коэффициента поворота: &amp;lt;math&amp;gt;e^{\frac{-ji\pi}{k}}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;- размер шага &amp;quot;бабочки&amp;quot;, &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;- номер итерации вычислений над группой элементов, размером &amp;lt;math&amp;gt;2k&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;(0\leq j &amp;lt; k )&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из двух вышеописанных этапов БПФ, бит-реверсирование является самым быстровыполнимым. Поэтому вычислительное ядро алгоритма будет лежать именно во втором этапе. А именно, им будет являться вычисление &amp;quot;бабочек&amp;quot;. Количество этапов выполнения БПФ, как описывалось выше, равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Учитывая количество этапов, размерность входного сигнала и идею деления на подмножества, всего &amp;quot;бабочек&amp;quot; получается &amp;lt;math&amp;gt;\frac{nlog_{2}n}{2}&amp;lt;/math&amp;gt;. При этом операция умножения выполняется &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; раз. Это связано с тем, что результаты вычисления &amp;quot;бабочки&amp;quot; &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из всего вышесказанного заключаем, что макроструктуру алгоритма легче всего описать при помощи рекурсии: &lt;br /&gt;
*&amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; преобразований Фурье порядка 2&lt;br /&gt;
*умножение &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; пар комплексных чисел&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; комплексных сложений &lt;br /&gt;
*снова преобразование Фурье&lt;br /&gt;
Также необходимо заметить, что количеством рекурсий будет количество этапов выполнения БПФ, то есть глубина рекурсии будет равняться &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&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;quot;бабочка&amp;quot;&lt;br /&gt;
*выполнение БПФ&lt;br /&gt;
*функция вывода результата (для тестирования)&lt;br /&gt;
----&lt;br /&gt;
&#039;&#039;&#039;главный программный код БПФ:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; *inputSignal = NULL;   &lt;br /&gt;
	complex&amp;lt;double&amp;gt; *outputSignal = NULL; &lt;br /&gt;
	int size = 0;&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;FFT. Serial version.&amp;quot; &amp;lt;&amp;lt; endl;       &lt;br /&gt;
	ProcessInitialization(inputSignal, outputSignal, size);&lt;br /&gt;
	bit(inputSignal, outputSignal, size);&lt;br /&gt;
	SerialFFTCalculation(outputSignal, size);&lt;br /&gt;
	PrintSignal(outputSignal, size); //в случае тестирования&lt;br /&gt;
	ProcessTermination(inputSignal, outputSignal);&lt;br /&gt;
	cin.get();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
=== функция бит-реверсирования ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void bit(complex&amp;lt;double&amp;gt; inputSignal[], complex&amp;lt;double&amp;gt; *outputSignal, int size)&lt;br /&gt;
{ // процесс бит-реверсирования&lt;br /&gt;
	int j = 0, i = 0;&lt;br /&gt;
	while (i &amp;lt; size) &lt;br /&gt;
	{&lt;br /&gt;
		if (j &amp;gt; i) &lt;br /&gt;
		{ &lt;br /&gt;
			outputSignal[i] = inputSignal[j];       &lt;br /&gt;
			outputSignal[j] = inputSignal[i]; &lt;br /&gt;
		}&lt;br /&gt;
		else       &lt;br /&gt;
			if (j == i)          &lt;br /&gt;
				outputSignal[i] = inputSignal[i];&lt;br /&gt;
		&lt;br /&gt;
		int m = size &amp;gt;&amp;gt; 1;     &lt;br /&gt;
		while ((m &amp;gt;= 1) &amp;amp;&amp;amp; (j &amp;gt;= m)) &lt;br /&gt;
		{ &lt;br /&gt;
			j -= m;       &lt;br /&gt;
			m = m &amp;gt;&amp;gt; 1; &lt;br /&gt;
		}     &lt;br /&gt;
		j += m;     &lt;br /&gt;
		i++;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация размерности входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessInitialization(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal, int &amp;amp;size) &lt;br /&gt;
{&lt;br /&gt;
	// инициализация размера входного сигнала  &lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		cout &amp;lt;&amp;lt; &amp;quot;Enter the input signal length: &amp;quot;;&lt;br /&gt;
		cin &amp;gt;&amp;gt; size;&lt;br /&gt;
		if (size &amp;lt; 4)&lt;br /&gt;
			cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be &amp;gt;= 4&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
		else&lt;br /&gt;
		{&lt;br /&gt;
			int tmpSize = size;&lt;br /&gt;
			while (tmpSize != 1)&lt;br /&gt;
			{&lt;br /&gt;
				if (tmpSize % 2 != 0)&lt;br /&gt;
				{&lt;br /&gt;
					cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be powers of two&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
					size = -1;&lt;br /&gt;
					break;&lt;br /&gt;
				}&lt;br /&gt;
				tmpSize /= 2;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	while (size &amp;lt; 4);  &lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Input signal length = &amp;quot; &amp;lt;&amp;lt; size &amp;lt;&amp;lt; endl; &lt;br /&gt;
	inputSignal = new complex&amp;lt;double&amp;gt;[size];   &lt;br /&gt;
	outputSignal = new complex&amp;lt;double&amp;gt;[size];&lt;br /&gt;
	DummyDataInitialization(inputSignal, size);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void DummyDataInitialization(complex&amp;lt;double&amp;gt;* mas, int size)&lt;br /&gt;
{	&lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)&lt;br /&gt;
	{&lt;br /&gt;
		mas[i] = int(rand() % 100);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== процесс очистки памяти ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessTermination(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal) &lt;br /&gt;
{ &lt;br /&gt;
	//очистка памяти&lt;br /&gt;
	delete[] inputSignal;   &lt;br /&gt;
	inputSignal = NULL;   &lt;br /&gt;
	delete[] outputSignal;   &lt;br /&gt;
	outputSignal = NULL; &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение операции &amp;quot;бабочка&amp;quot; ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void Butterfly(complex&amp;lt;double&amp;gt; *signal, complex&amp;lt;double&amp;gt; &amp;amp;u,int offset, int butterflySize)&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; tem = signal[offset + butterflySize] * u;&lt;br /&gt;
	signal[offset + butterflySize] = signal[offset] - tem;   &lt;br /&gt;
	signal[offset] += tem;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение БПФ ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void SerialFFTCalculation(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	int m = 0;   &lt;br /&gt;
	for (int tmp_size = size; tmp_size &amp;gt; 1; tmp_size /= 2, m++);&lt;br /&gt;
	for (int p = 0; p &amp;lt; m; p++)&lt;br /&gt;
	{&lt;br /&gt;
		int butterflyOffset = 1 &amp;lt;&amp;lt; (p + 1);&lt;br /&gt;
		int butterflySize = butterflyOffset &amp;gt;&amp;gt; 1;&lt;br /&gt;
		double coeff = M_PI / butterflySize;&lt;br /&gt;
&lt;br /&gt;
		for (int i = 0; i &amp;lt; size / butterflyOffset; i++)&lt;br /&gt;
			for (int j = 0; j &amp;lt; butterflySize; j++)&lt;br /&gt;
				Butterfly(signal,complex&amp;lt;double&amp;gt;(cos(-j*coeff),sin(-j*coeff)),j+i*butterflyOffset,butterflySize);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== функция вывода результата (для тестирования) ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void PrintSignal(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Result signal&amp;quot; &amp;lt;&amp;lt; endl;   &lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)     &lt;br /&gt;
		cout &amp;lt;&amp;lt; signal[i] &amp;lt;&amp;lt; endl;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотренный алгоритм  Cooley – Tukey выполняется за &amp;lt;math&amp;gt;nlog_{2}n&amp;lt;/math&amp;gt; операций комплексного сложения и &amp;lt;math&amp;gt;\frac{n}{2}log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения. Таким образом последовательная сложность алгоритма является &#039;&#039;&#039;линейно-логарифмической&#039;&#039;&#039;.&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[file:Граф.jpg|center|thumb|800px|Рисунок 1. Алгоритм Cooley – Tukey для n=4, при предварительно сделанном бит-реверсировании]]&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
[[file:Парал.jpg|center|thumb|800px|Рисунок 2. Распараллеливание алгоритма Cooley – Tukey для n=8]]&lt;br /&gt;
Рисунок 2 получен из информационного графа, главной идеей которого является процедура деления множеств полученных на предыдущем шаге на два равных по мощности подмножества и применение к ним БПФ. То есть процесс вычисления распределяется между &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; процессорами. И так как при последовательной реализации алгоритма было &amp;lt;math&amp;gt;\frac{n}{2}\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;n\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. Получается, что при параллельной реализации алгоритма мы будем иметь &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;2\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. При этом, если используется функция HT(Hyper threading), то параллельная сложность быстрого преобразования Фурье (базовый алгоритм Кули-Тьюки) для векторов с длиной, равной степени двойки составляет &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt;.&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
*&#039;&#039;&#039;Вещественное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{R}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
*&#039;&#039;&#039;Комплексное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
*Соотношение последовательной и параллельной сложности является &#039;&#039;линейным&#039;&#039;. Это хорошо видно из пункта [[#Ресурс параллелизма алгоритма|ресурс параллелизма алгоритма]].&lt;br /&gt;
*Вычислительная мощность алгоритма является &#039;&#039;логарифмической&#039;&#039;.&lt;br /&gt;
*Устойчивость. &lt;br /&gt;
**Рассмотрим вопрос об устойчивости поближе. Так как БПФ отличается от ДПФ простым лишь сокращением одинаковых операций, то вопрос устойчивости можно рассматривать на примере ДПФ. Так, к примеру, возьмем формулу ДПФ и добавим возмущение: &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}(x_{k}+\varepsilon )e^{\frac{-2*\pi*k*p*i}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. После чего преобразуем ее: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2*\pi*k*p*i}{n}}+\varepsilon\sum_{k=0}^{n-1} e^{\frac{-2*\pi*k*p*i}{n}}&amp;lt;/math&amp;gt; и оценим вторую сумму, которая является геометрической прогрессией со знаменателем &amp;lt;math&amp;gt;q=e^{\frac{-2*\pi*p*i}{n}}&amp;lt;/math&amp;gt;. Таким образом оцениваемая сумма будет равняться: &amp;lt;math&amp;gt;\varepsilon\frac{1-e^{\frac{-2*\pi*p*i*(n-1))}{n}}}{1-e^{\frac{-2*\pi*p*i}{n}}}&amp;lt;/math&amp;gt;. При достаточно больших размерностях входного сигнала данное выражение сводится к: &amp;lt;math&amp;gt;\varepsilon (1-e^{-2*\pi*p*i})&amp;lt;/math&amp;gt;. В тригонометрической форме данного выражения: &amp;lt;math&amp;gt;\varepsilon (cos(\frac{2*\pi*p*(n-1)}{n})-i*sin(\frac{2*\pi*p*(n-1)}{n}))&amp;lt;/math&amp;gt; видно, что принимая во внимание целочисленность индексов &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; получается 0. Таким образом, мы можем говорить об устойчивости алгоритма в целом, и абсолютной устойчивости алгоритма в асимптотике.&lt;br /&gt;
*исходя из информационного графа, можно сделать вывод о сбалансированности алгоритма.&lt;br /&gt;
*алгоритм полностью детерминирован.&lt;br /&gt;
*Степень исхода вершины информационного графа не превышает двух.&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Вычисления проводились на суперкомпьютере «Ломоносов». &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Основные технические характеристики суперкомпьютера &amp;quot;Ломоносов&amp;quot;:&#039;&#039;&#039;&lt;br /&gt;
*Пиковая производительность: 1,7 Пфлопс&lt;br /&gt;
*Производительность на тесте Linpack: 901.9 Тфлопс&lt;br /&gt;
*Число вычислительных узлов х86: 5 104&lt;br /&gt;
*Число графических вычислительных узлов: 1 065&lt;br /&gt;
*Число вычислительных узлов PowerXCell: 30&lt;br /&gt;
*Число процессоров/ядер x86: 12 346 / 52 168&lt;br /&gt;
*Число графических ядер: 954 240&lt;br /&gt;
*Оперативная память: 92 ТБ&lt;br /&gt;
*Общий объем дисковой памяти вычислителя: 1,75 ПБ&lt;br /&gt;
*Основной тип процессора: Intel Xeon  X5570/Intel Xeon 5670, Nvidia X2070&lt;br /&gt;
*Число типов вычислительных узлов: 8&lt;br /&gt;
*Основной тип вычислительных узлов: TB2-XN&lt;br /&gt;
*Все узлы связаны тремя независимыми сетями:&lt;br /&gt;
**Системная сеть - QDR InfiniBand, 40 Гбит/сек&lt;br /&gt;
**Сервисная сеть - Ethernet, 10 Гбит/сек, 1 Гбит/сек и 100 Мбит/сек &lt;br /&gt;
**Управляющая сеть - Ethernet, 10 Гбит/сек и 1 Гбит/сек &lt;br /&gt;
**Сеть барьерной синхронизации и сеть глобальных прерываний, Т-Платформы&lt;br /&gt;
При работе была использована библиотека FFTW последней версии (3.3.5). Скрипт mpicxx с компилятором Intel. Вычислительные узлы х86. Код исследуемой программы представлен в разделе [[#Существующие реализации алгоритма|Существующие реализации алгоритма]]. &lt;br /&gt;
[[file:масш.jpg|center|thumb|1000px|Рисунок 3. Параллельная реализация алгоритма. Изменение эффективности(%)в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
Как описывалось выше, каждый процессор получает достаточно низкий объем работы, что приводит к неэффективному распараллеливанию алгоритма (высокие затраты на передачу данных между процессорами по сравнению с вычислениями на них). Поэтому необходимо находить оптимальное количество процессоров для каждой отдельно взятой задачи. В данном примере, при размерности входного сигнала менее 262144, оптимальное количество процессоров составляет 1. При размерности 262144-4194304 оптимум достигался при 8 процессорах. И при размерности более 4194304- 16 процессоров. &lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Самой распространенной и известной реализацией дискретного БПФ является библиотека [http://www.fftw.org/ FFTW], разработанная Маттео Фриго и Стивеном Г. Джонсоном в Массачусетском технологическом институте. Данная библиотека существует для языков C и FORTRAN и позволяет производить вычисления в одном или нескольких измерениях. Входной сигнал может быть как действительным так и комплексным, а его размерность не обязательно должна быть степенью двойки. На данный момент последняя версия FFTW 3.3.5. Другие особенности библиотеки:&lt;br /&gt;
*Скорость. (Поддержка SSE / SSE2 / Altivec, начиная с версии 3.0. Версия 3.3.1 поддерживает AVX и ARM Neon.)&lt;br /&gt;
*Параллельные вычисления (OpenMP, MPI).&lt;br /&gt;
*[http://www.fftw.org/fftw3_doc/ Документация] в форматах HTML, PDF.&lt;br /&gt;
*Данное программное обеспечение является свободным и распространяется по лицензии GNU (General Public License).&lt;br /&gt;
Здесь приводится пример реализации прямого БПФ для комплексных данных входного сигнала на языке C++. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;fftw3-mpi.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/stat.h&amp;gt;&lt;br /&gt;
#include &amp;lt;fcntl.h&amp;gt;&lt;br /&gt;
# include &amp;lt;time.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
	const ptrdiff_t N0 = 4194304;&lt;br /&gt;
	fftw_plan planForw;&lt;br /&gt;
	fftw_complex *data, *dataOut;&lt;br /&gt;
	ptrdiff_t alloc_local, local_ni, local_i_start, i, j, local_no, local_o_start;&lt;br /&gt;
	int index, size;&lt;br /&gt;
	double startwtime, endwtime;&lt;br /&gt;
	MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
	fftw_mpi_init();&lt;br /&gt;
	MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;index);&lt;br /&gt;
	MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);&lt;br /&gt;
&lt;br /&gt;
	/* get local data size and allocate */&lt;br /&gt;
	alloc_local = fftw_mpi_local_size_1d(N0, MPI_COMM_WORLD, FFTW_FORWARD, FFTW_ESTIMATE,&lt;br /&gt;
		&amp;amp;local_ni, &amp;amp;local_i_start, &amp;amp;local_no, &amp;amp;local_o_start);&lt;br /&gt;
	data = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	dataOut = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	/* create plan  */&lt;br /&gt;
	planForw = fftw_mpi_plan_dft_1d(N0, data, dataOut, MPI_COMM_WORLD,&lt;br /&gt;
		FFTW_FORWARD, FFTW_ESTIMATE);&lt;br /&gt;
	/* initialize data to some function my_function(x,y) */&lt;br /&gt;
	for (i = 0; i &amp;lt; local_ni; ++i)&lt;br /&gt;
	{&lt;br /&gt;
		data[i][0] = rand() / (double)RAND_MAX;&lt;br /&gt;
		data[i][1] = rand() / (double)RAND_MAX;&lt;br /&gt;
	}&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		startwtime = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_execute(planForw);&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		endwtime = MPI_Wtime();&lt;br /&gt;
		printf(&amp;quot;wall clock time = %f\n&amp;quot;,&lt;br /&gt;
			endwtime - startwtime);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_destroy_plan(planForw);&lt;br /&gt;
	MPI_Finalize();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
[http://www.fftw.org/download.html Скачав] библиотеку и установив ее на рабочий компьютер, можно поэкспериментировать, используя вышеописанный исходный код. При этом, на стадии компиляции необходимо указывать директорию, где находятся заголовочные файлы и директорию, где находятся библиотечные файлы. Пример: mpicxx name.cpp -I/path/to/include/files -L/path/to/lib/files  -lfftw3_mpi -lfftw3 -lm. Для версии 2.1.4 описание на русском языке приводится [https://parallel.ru/cluster/fftw.html здесь].&lt;br /&gt;
&lt;br /&gt;
Остальные реализации:&lt;br /&gt;
*[http://www.gnu.org/software/gsl/ GSL - GNU Scientific Library]&lt;br /&gt;
*[https://developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
*[http://www-03.ibm.com/systems/power/software/essl/ Engineering and Scientific Subroutine Library (ESSL) and Parallel ESSL]&lt;br /&gt;
=Литература=&lt;br /&gt;
[1] Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний &lt;br /&gt;
&lt;br /&gt;
[2] Г. Нуссбаумер. Быстрое преобразование Фурье и алгоритмы вычисления сверток &lt;br /&gt;
&lt;br /&gt;
[3] А.О. Лапаев Параллельное вычисление дискретного преобразования Фурье полинома в простом поле&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Victoria_Stepanischeva&amp;diff=18703</id>
		<title>Обсуждение участника:Victoria Stepanischeva</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Victoria_Stepanischeva&amp;diff=18703"/>
		<updated>2016-11-21T09:32:05Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Замечания к статье ===&lt;br /&gt;
&lt;br /&gt;
* содержание не соответствуют шаблону описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:21, 25 октября 2016 (MSK)&lt;br /&gt;
* не указан конкретный вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:21, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 1.9 не раскрыт--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:21, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:46, 16 ноября 2016 (MSK)&lt;br /&gt;
** в п.п. 2.4 следует добавить подробное описание компьютерной платформы и описание условий, в которых производились эксперименты: (какой компилятор, с какими опциями использовался, какие версии библиотек, на каких узлах проводился запуск и т.д.)--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 12:32, 21 ноября 2016 (MSK)  &lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* Раздел про общее описание алгоритма слишком большой и нарезан на много частей. Было бы лучше написать статью про саму задачу преобразования Фурье, где и упоминались бы в частности области применения, а также разные алгоритмы с краткими описаниями (а впоследствии со ссылками на статьи с полным описанием) [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:56, 11 ноября 2016 (MSK)&lt;br /&gt;
* При упоминании рекурсивного решения задачи следует сказать о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:56, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Victoria_Stepanischeva/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(FFT)&amp;diff=18702</id>
		<title>Участник:Victoria Stepanischeva/Быстрое преобразование Фурье (FFT)</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:Victoria_Stepanischeva/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(FFT)&amp;diff=18702"/>
		<updated>2016-11-21T09:31:12Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Авторы:&lt;br /&gt;
&lt;br /&gt;
* [[Участник:Victoria_Stepanischeva|Степанищева В.С.]], группа 601 (Общее описание алгоритма, Вычислителное ядро алгоритма, Макроструктура алгоритма, Информационный граф, Ресурс параллелизма алгоритма)&lt;br /&gt;
* [[Участник:Igor_Munkin|Мунькин И.А.]], группа 601 (Математическое описание алгоритма, Схема реализации последовательного алгоритма, Последовательная сложность алгоритма, Входные и выходные данные алгоритма, Свойства алгоритма, Существующие реализации алгоритма)&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразования Фурье&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \; log \, N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(log \,N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&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;
[https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss Карлом Фридрихом Гауссом]&lt;br /&gt;
еще в 1805 году для интерполяции траекторий астероидов, однако эта работа не&lt;br /&gt;
получила широкого признания. Алгоритм быстрого преобразования Фурье обрел свою&lt;br /&gt;
популярность благодаря работе&lt;br /&gt;
[https://en.wikipedia.org/wiki/James_Cooley James Cooley], работающего в то&lt;br /&gt;
время в [https://en.wikipedia.org/wiki/IBM IBM], и&lt;br /&gt;
[https://en.wikipedia.org/wiki/John_Tukey John Tukey], параллельно принимающего&lt;br /&gt;
участие в разработках [https://en.wikipedia.org/wiki/Bell_Labs Bell Labs] и&lt;br /&gt;
работающего в&lt;br /&gt;
[https://en.wikipedia.org/wiki/Princeton_University Принстонском университетe].&lt;br /&gt;
В их статье, изданной в 1965 году, они представили подробное описание алгоритма&lt;br /&gt;
и его программной реализации.&lt;br /&gt;
&lt;br /&gt;
====Разновидности алгоритма быстрого преобразования Фурье====&lt;br /&gt;
Помимо традиционного и наиболее распространенного алгоритма&lt;br /&gt;
[https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Cooley-Tukey]&lt;br /&gt;
существует также несколько других часто используемых реализаций алгоритма&lt;br /&gt;
быстрого преобразования Фурье.&lt;br /&gt;
&lt;br /&gt;
=====PFA-алгоритм=====&lt;br /&gt;
Алгоритм [https://en.wikipedia.org/wiki/Prime-factor_FFT_algorithm prime-factor]&lt;br /&gt;
(PFA), также называемый алгоритмом Good-Thomas, представляет собой алгоритм&lt;br /&gt;
быстрого преобразования Фурье, который представляет дискретное преобразование&lt;br /&gt;
Фурье размера &amp;lt;math&amp;gt;N = N_1 \cdot N_2&amp;lt;/math&amp;gt; в виде двумерного&lt;br /&gt;
&amp;lt;math&amp;gt;N_1 \times N_2&amp;lt;/math&amp;gt; дискретного преобразования Фурье, при условии, что&lt;br /&gt;
&amp;lt;math&amp;gt;N_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;N_2&amp;lt;/math&amp;gt; - взаимно простые числа. Представленные&lt;br /&gt;
преобразования меньшего размера могут быть вычислены либо рекурсивным&lt;br /&gt;
применением PFA либо используя какой-либо иной метод быстрого преобразования&lt;br /&gt;
Фурье.&lt;br /&gt;
&lt;br /&gt;
=====Алгоритм Bluestein=====&lt;br /&gt;
Алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Bluestein],&lt;br /&gt;
обычно называемый &amp;lt;math&amp;gt;\mathcal{Z}&amp;lt;/math&amp;gt;-чирп алгоритмом, представляет собой&lt;br /&gt;
алгоритм быстрого преобразования Фурье, который вычисляет дискретное&lt;br /&gt;
преобразование Фурье различных размеров с помощью представления оного в виде&lt;br /&gt;
линейной свертки. Другой алгоритм быстрого преобразования Фурье, использующий&lt;br /&gt;
свойства простых чисел, алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Rader], также вычисляет&lt;br /&gt;
дискретное преобразование Фурье в виде свертки.&lt;br /&gt;
&lt;br /&gt;
====Приложения быстрого преобразования Фурье====&lt;br /&gt;
&lt;br /&gt;
Алгоритм быстрого преобразования Фурье имеет множество областей приложения,&lt;br /&gt;
таких как:&lt;br /&gt;
* Обработка изображений&lt;br /&gt;
* Цифровое сжатие&lt;br /&gt;
* Численное интегрирование&lt;br /&gt;
* Быстрые методы решения уравнения Пуассона&lt;br /&gt;
* Спектральные методы решения обыкновенных дифференциальных уравнений&lt;br /&gt;
&lt;br /&gt;
Рассмотрим подробнее те приложения, с которыми мы наиболее часто сталкиваемся&lt;br /&gt;
в повседневной жизни.&lt;br /&gt;
&lt;br /&gt;
=====Обработка изображений=====&lt;br /&gt;
Все фильтры делят на две группы: пространственные и частотные. Типов&lt;br /&gt;
пространственных фильтров достаточно много: линейные, медианные, изотропные и&lt;br /&gt;
др. Процесс фильтрации в этих фильтрах основан на простом применении маски&lt;br /&gt;
фильтра ко всем точкам изображения. В каждой точке отклик фильтра вычисляется с&lt;br /&gt;
использованием предварительно заданных связей. Все пространственные фильтры&lt;br /&gt;
имеют линейную трудоемкость.&lt;br /&gt;
&lt;br /&gt;
Частотная фильтрация является более общим механизмом фильтрации, чем&lt;br /&gt;
пространственная. Все пространственные фильтры можно реализовать с помощью&lt;br /&gt;
частотных. Частотные фильтры выполняют фильтрацию изображений в частотной&lt;br /&gt;
области. Для этого с помощью преобразования Фурье происходит получение спектра&lt;br /&gt;
изображения. После применения фильтра к спектру изображения выполняется обратное&lt;br /&gt;
преобразование Фурье и получается отфильтрованное изображение.&lt;br /&gt;
&lt;br /&gt;
Простота фильтрации в частотной области заключается в простой интерпретации&lt;br /&gt;
частот:&lt;br /&gt;
* низкие частоты - плавное изменение яркости изображения&lt;br /&gt;
* высокие частоты - быстрое изменение яркости изображения (границы объектов)&lt;br /&gt;
За счет изменения значений определенных частот можно добиться размытия&lt;br /&gt;
изображения, повышение резкости и др.&lt;br /&gt;
&lt;br /&gt;
=====Цифровое сжатие=====&lt;br /&gt;
Одним из первых форматов файла-контейнера для хранения записи оцифрованного&lt;br /&gt;
аудипотока является формат WAV. Этот формат чаще всего используется в качестве&lt;br /&gt;
оболочки для несжатого звука. При таком кодировании в WAV-файле хранится полная&lt;br /&gt;
информация об исходном звуке, оцифрованном и проквантованном с некоторой&lt;br /&gt;
частотой, например 44 кГц. Этой информации абсолютно достаточно для&lt;br /&gt;
воспроизведения всех частот исходного сигнала, меньших половины частоты&lt;br /&gt;
квантования (по [https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%9A%D0%BE%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B8%D0%BA%D0%BE%D0%B2%D0%B0 теореме Котельникова] ). При этом одна&lt;br /&gt;
минута записи занимает около 15 Мб, поэтому в настоящее время очень популярным&lt;br /&gt;
стал формат кодирования MPEG Layer-3 со сжатием.&lt;br /&gt;
&lt;br /&gt;
При сжатии кодеком MPEG Layer-3 исходный звуковой сигнал разбивается на&lt;br /&gt;
фрагменты, длительностью по 50 миллисекунд, каждый из которых анализируется&lt;br /&gt;
отдельно. При анализе фрагмент раскладывается на гармоники по методу Фурье, из&lt;br /&gt;
которых в соответствии с теорией восприятия звука человеческим ухом&lt;br /&gt;
выбрасываются те гармоники, которые человек хуже воспринимает на фоне остальных:&lt;br /&gt;
* более тихие гармоники на фоне более громких&lt;br /&gt;
* звуки, замаскированные вследствие инертности слуха (так, например, если за очень громким хлопком, с задержкой в долю секунды, пойдет какой-то иной кратковременный сигнал, то его слышно не будет)&lt;br /&gt;
&lt;br /&gt;
При воспроизведении файла закодированного MPEG Layer-3 производится обратное&lt;br /&gt;
преобразование, при котором оставшиеся гармоники вновь преобразуются в звуковую&lt;br /&gt;
волну. Поскольку часть информации об исходном сигнале была удалена, звук&lt;br /&gt;
получается не совпадающий с исходным. Одной из основных характеристик качества&lt;br /&gt;
аудио потока является битрейт.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Битрейт&#039;&#039; - это объем информации на каждую секунду записи:&lt;br /&gt;
* чем он меньше, тем меньший размер имеют файлы с одинаковой по времени длине&lt;br /&gt;
* чем он меньше, тем большее количество &amp;quot;лишних&amp;quot; гармоник приходится удалять&lt;br /&gt;
&lt;br /&gt;
===Математическое описание алгоритма===&lt;br /&gt;
Пусть задан вектор действительных чисел&lt;br /&gt;
&amp;lt;math&amp;gt;\left(f_1, f_2, f_3, \dots, f_{N-1} \right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Дискретное преобразование Фурье определено следующим образом&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_k = \sum_{n = 0}^{N - 1}\, f_n\, e^{\left(\frac{i2\pi kn}{N}\right)}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обозначим &amp;lt;math&amp;gt;W_N = e^{\left(\frac{i2\pi}{N}\right)}&amp;lt;/math&amp;gt; - &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;-ый&lt;br /&gt;
корень единицы. Таким образом &amp;lt;math&amp;gt;(W_N)^N = 1&amp;lt;/math&amp;gt;. Несложные математические&lt;br /&gt;
преобразования приводят формулу дискретного преобразования Фурье к следующему&lt;br /&gt;
виду&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_k = \sum_{n = 0}^{N - 1}\, f_n\, W_n^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, алгоритм вычисления дискретного преобразования Фурье состоит из&lt;br /&gt;
2 шагов:&lt;br /&gt;
# Вычисление поворотных коэффициентов &amp;lt;math&amp;gt;W_n^{-nk},\; n,k = 0, 1, \dots, N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Вычисление коэффициентов Фурье &amp;lt;math&amp;gt;F_k,\; k = 0, 1, \dots, N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В основе концепции быстрого преобразования Фурье лежит идея отдельного расчета четных и&lt;br /&gt;
нечетных коэффициентов с использованием векторов меньшего размера. Разобъем дискретное&lt;br /&gt;
преобразование Фурье следующим образом&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_k = \sum_{n = 0}^{N - 1}\, f_n\, W_{N}^{-nk}&lt;br /&gt;
        = \sum_{n = 0}^{N/2 - 1}\, g_n\, W_{N/2}^{-nk}&lt;br /&gt;
        + \sum_{n = 0}^{N/2 - 1}\, h_n\, W_{N/2}^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следовательно, четные и нечетные коэффициенты Фурье могут быть записаны в&lt;br /&gt;
следующем виде:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_{2k} = \sum_{n = 0}^{N/2 - 1}\, \underbrace{&lt;br /&gt;
        \left(f_n + f_{n + N/2}\right)&lt;br /&gt;
}_{g_n} \, W_{N/2}^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_{2k+1} = \sum_{n = 0}^{N/2 - 1}\, \underbrace{&lt;br /&gt;
        \left((f_n + f_{n + N/2}) W^{-n}\right)&lt;br /&gt;
}_{h_n} \, W_{N/2}^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, вычисление дискретного преобразования Фурье от &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
коэффициентов сводится к вычислению 2 дискретных преобразований Фурье от&lt;br /&gt;
&amp;lt;math&amp;gt;N/2&amp;lt;/math&amp;gt; коэффициентов. Применяя данную идею рекурсивно, получаем&lt;br /&gt;
алгоритм быстрого преобразования Фурье.&lt;br /&gt;
&lt;br /&gt;
===Вычислительное ядро алгоритма===&lt;br /&gt;
&lt;br /&gt;
[[File:Stepmoon_buttefly.png|left|200px]]&lt;br /&gt;
В случае размера входных данных равному &amp;lt;math&amp;gt;N = 2^M&amp;lt;/math&amp;gt; вычислительным&lt;br /&gt;
ядром алгоритма являются так называемые &amp;quot;бабочки&amp;quot;. В элементарном случае&lt;br /&gt;
&amp;quot;бабочка&amp;quot; является двухточечным преобразованием.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
g_n = f_n + f_{n + N/2}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
h_n = \left(f_n - f_{n + N/2}\right)\, W^{-n}&lt;br /&gt;
&amp;lt;/math&amp;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/Divide_and_conquer_algorithms &amp;quot;разделяй и властвуй&amp;quot;].&lt;br /&gt;
&lt;br /&gt;
Рассмотрим общую структуру для входных данных размера &amp;lt;math&amp;gt;N = N1 \cdot N2&amp;lt;/math&amp;gt;&lt;br /&gt;
# Исходное множество точек разбивается на &amp;lt;math&amp;gt;N2&amp;lt;/math&amp;gt; равных подмножеств размером &amp;lt;math&amp;gt;N1&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Над каждым из полученных множеств выполняется быстрое преобразование Фурье. Для его вычисления над каждым из подмножеств рассматриваемого множества снова выполняется деление на равные части. Такое разбиение продолжается до тех пор, пока количество элементов, над которыми необходимо выполнить быстрое преобразование Фурье, не будет равно одному.&lt;br /&gt;
# После этого выполняется объединение всех промежуточных результатов. Полученные на предыдущем шаге множества объединяются за линейное время.&lt;br /&gt;
&lt;br /&gt;
Ниже представлена схема алгоритма Cooley-Tukey для &amp;lt;math&amp;gt;N = 2^M&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Stepmoon_macro.png|center|800px]]&lt;br /&gt;
&lt;br /&gt;
===Схема реализации последовательного алгоритма===&lt;br /&gt;
Ниже представлен прототип реализации для &amp;lt;math&amp;gt;N = 2^M&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;source lang=perl&amp;gt;&lt;br /&gt;
use Math::Complex;&lt;br /&gt;
&lt;br /&gt;
sub fft {&lt;br /&gt;
    my $input = shift;&lt;br /&gt;
    return $input if @{ $input } == 1;&lt;br /&gt;
    my @w = map {&lt;br /&gt;
        my $theta = 2 * pi * $_ / @{ $input };&lt;br /&gt;
        Math::Complex-&amp;gt;make(cos($theta), sin($theta));&lt;br /&gt;
    } (0 .. $#input);&lt;br /&gt;
    my $odd  = fft([ map { $input-&amp;gt;[2 * $_ + 1] }, (0 .. @{ $input } / 2 - 1) ]);&lt;br /&gt;
    my $even = fft([ map { $input-&amp;gt;[2 * $_] }, (0 .. @{ $input } / 2 - 1) ]);&lt;br /&gt;
    return [ map {&lt;br /&gt;
        $even-&amp;gt;[$_ % @{ $even }] + $w[$_] * $odd-&amp;gt;[$_ % @{ $odd }];&lt;br /&gt;
    } (0 .. $#input) ];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Последовательная сложность алгоритма===&lt;br /&gt;
Если считать только главные члены выражений для последовательной сложности алгоритма, то простой алгоритм Cooley-Tukey может быть выполнен за &amp;lt;math&amp;gt;N\, log\, N&amp;lt;/math&amp;gt; операций. Таким образом, простой алгоритм Cooley-Tukey относится к линейно-логарифмическому классу по последовательной сложности.&lt;br /&gt;
&lt;br /&gt;
===Информационный граф===&lt;br /&gt;
Ниже представлен информационный граф для простого алгоритма Cooley-Tukey для &amp;lt;math&amp;gt;N = 8&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;\;h&amp;lt;/math&amp;gt; - операция сложения двух комплексных чисел, &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; - операция вычитания двух комплексных чисел и умножения результата вычитания на комплексное число (поворотный множитель). В последнем столбце операций умножения не производится.&lt;br /&gt;
&lt;br /&gt;
[[File:Stepmoon_Info_graph.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
===Ресурс параллелизма алгоритма===&lt;br /&gt;
Простой алгоритм Cooley-Tukey имеет критический путь, состоящий из&lt;br /&gt;
&amp;lt;math&amp;gt;log\, N&amp;lt;/math&amp;gt; операций сложения и вычитания и &amp;lt;math&amp;gt;log\, N&amp;lt;/math&amp;gt;&lt;br /&gt;
операций комплексного умножения (основание логарифма зависит от выбранного на&lt;br /&gt;
каждом шаге алгоритма разбиения). Следовательно, простой алгоритм быстрого&lt;br /&gt;
преобразования Фурье в общем случае может быть отнесен к логарифмическому классу&lt;br /&gt;
по параллельной сложности.&lt;br /&gt;
&lt;br /&gt;
===Входные и выходные данные алгоритма===&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; измеренные значения сигнала в дискретных временных точках с номерами &amp;lt;math&amp;gt;0 \dots N&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;f \in \mathbb{R}^N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных:&#039;&#039;&#039; &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; вектор компонент спектра исходного сигнала: &amp;lt;math&amp;gt;F \in \mathbb{C}^N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных:&#039;&#039;&#039; &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Свойства алгоритма===&lt;br /&gt;
Обработку подмножеств, получаемых после разбиения, можно выполнять независимо,&lt;br /&gt;
этот факт можно использовать при распараллеливании алгоритма.&lt;br /&gt;
В случае рекурсивной реализации глубина рекурсии составит &amp;lt;math&amp;gt;log\, N&amp;lt;/math&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;
* число процессоров [ 5 : 45 ] с шагом 5&lt;br /&gt;
* размер вектора [ 256 : 16384 ] с шагом равным степени двойки&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график производительности выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
[[File:Stepmoon_performance.png|center|800px]]&lt;br /&gt;
&lt;br /&gt;
[https://github.com/FFTW/fftw3 Текст исследуемой программы]&lt;br /&gt;
&lt;br /&gt;
===Динамические характеристики и эффективность реализации алгоритма===&lt;br /&gt;
&lt;br /&gt;
===Выводы для классов архитектур===&lt;br /&gt;
&lt;br /&gt;
===Существующие реализации алгоритма===&lt;br /&gt;
Наиболее популярной библиотекой, содержащей реализацию быстрого преобразования&lt;br /&gt;
Фурье, является [http://fftw.org/ FFTW], изначально разработанная сотрудниками&lt;br /&gt;
[https://en.wikipedia.org/wiki/Massachusetts_Institute_of_Technology MIT]&lt;br /&gt;
Matteo Frigo и Steven G. Johnson. На сегодняшний день исходный код библиотеки&lt;br /&gt;
распространяется под свободной лицензией&lt;br /&gt;
[https://en.wikipedia.org/wiki/GNU_General_Public_License#Version_2 GNU General Public Licence v2]&lt;br /&gt;
с помощью сервиса [https://github.com/FFTW/fftw3 Github], тем самым давая&lt;br /&gt;
возможность сообществу принимать участие в разработке. Несмотря на то, что сама&lt;br /&gt;
библиотека [http://fftw.org/faq/section2.html#whynotfortran написана на языке C]&lt;br /&gt;
существуют интерфейсы для работы с ней используя такие языки программирования&lt;br /&gt;
как Fortran, C++ и другиe.&lt;br /&gt;
&lt;br /&gt;
Для разбиения преобразований, размеры которых являются составным числом, на&lt;br /&gt;
меньшие используется одна из многочисленных реализаций алгоритма&lt;br /&gt;
[https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Cooley-Tukey]&lt;br /&gt;
(в зависимости от разложения на простые множители и паттернов доступа к памяти).&lt;br /&gt;
В свою очередь для преобразований, размеры которых являются простыми числом&lt;br /&gt;
используется либо алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Raider], либо алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Bluestein].&lt;br /&gt;
Библиотека признана самой быстрой реализацией быстрого преобразования Фурье,&lt;br /&gt;
гарантируя на любых объемах входных данных сложность&lt;br /&gt;
&amp;lt;math&amp;gt;O \left(n\; log\, n \right)&amp;lt;/math&amp;gt;.  Существует реализация FFTW и с&lt;br /&gt;
поддержкой [https://en.wikipedia.org/wiki/Message_Passing_Interface MPI],&lt;br /&gt;
позволяющая работать с данными не помещающимися в память. Однако перегруппировка&lt;br /&gt;
данных приводит к оверхеду, которого трудно избежать при in-place вычислении&lt;br /&gt;
преобразований различных размеров.&lt;br /&gt;
&lt;br /&gt;
==Литература==&lt;br /&gt;
[1] L. Rabiner, B. Gold, &#039;&#039;&amp;quot;Theory and application of digital signal processing&amp;quot;&#039;&#039; // Prentice Hall, New Jersey, 1975.&lt;br /&gt;
&lt;br /&gt;
[2] Бахвалов Н.С., Жидков Н.П., Кобельков Г.М. &#039;&#039;&amp;quot;Численные методы&amp;quot;&#039;&#039; // БИНОМ. Лаборатория знаний, Москва, 2008.&lt;br /&gt;
&lt;br /&gt;
[3] M. Frigo, S. G. Johnson &#039;&#039;&amp;quot;FFTW Documentation&amp;quot;&#039;&#039; // [http://fftw.org/fftw3_doc/ fftw.org]&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=18700</id>
		<title>Обсуждение участника:Timokhinivan</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=18700"/>
		<updated>2016-11-21T09:30:15Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4-2.6 не соответствуют содержанию описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
* не указаны авторы статьи и их вклад--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
** Автор один, я (Тимохин Иван, 603 гр. ВМК МГУ) --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 14:50, 27 октября 2016 (MSK)&lt;br /&gt;
*** Так укажите себя, чтобы не было таких вопросов--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 31 октября 2016 (MSK).&lt;br /&gt;
* в п.п. 2.4 следует добавить исходный код программы и описание компьютерной платформы,  с подробным описанием условий, в которых производились эксперименты.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:37, 18 ноября 2016 (MSK)&lt;br /&gt;
** Исходный код нужен даже если это просто вызов библиотечной функции? --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 23:55, 18 ноября 2016 (MSK)&lt;br /&gt;
*** да.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 12:30, 21 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* В общем описании алгоритма нужны слова именно про алгоритм: следует рассказать идею быстрого преобразования, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не вполне понятны слова &amp;quot;первообразный корень из 1 степени N&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* В последней формуле раздела 1.2 индексы k и l, кажется, должны идти через запятую. Если там рассматривается их произведение, следует уточнить, какие значения принимает каждый из них [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2.3 - понятие &amp;quot;порядок преобразования&amp;quot; нуждается в пояснении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не хватает слов о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9&amp;diff=18695</id>
		<title>Обсуждение участника:Роман Землянский</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9&amp;diff=18695"/>
		<updated>2016-11-21T09:16:34Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п. 1.1 &amp;quot;...собственные векторы трехдиагональной матриц,&amp;quot; заменить на &amp;quot;...собственные векторы трехдиагональной матрицы,&amp;quot;--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:48, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 1.7 приведенный рисунок не является рисунком информационного графа--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:48, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 1.8, 1.9 оценка не произведена--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:48, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:26, 16 ноября 2016 (MSK)&lt;br /&gt;
** в п.п. 2.4 следует добавить код исследуемой программы, а также подробное описание условий, в которых производились эксперименты: характеристики программно-аппаратной платформы (какой компилятор, с какими опциями использовался, какие версии библиотек, на каких узлах проводился запуск и т.д.)--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 12:16, 21 ноября 2016 (MSK)  &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Второй этап проверки ==&lt;br /&gt;
&lt;br /&gt;
=== Итерация 1 ===&lt;br /&gt;
&lt;br /&gt;
Начальные замечания и комментарии (возможно будут появляться новые, пишите, пожалуйста, комментарии по ходу исправлений и ответы):&lt;br /&gt;
&lt;br /&gt;
* Исправить ошибки в формулах в разделе 1.2&lt;br /&gt;
* Исправить ошибку в разделе 1.9&lt;br /&gt;
* Раздел 2.7 - как это не найдены реализации, если в пункте 1.1 они указаны?&lt;br /&gt;
* По информационному графу не видно, что дерево может быть большей высоты, нужно бы это как-то отразить. И не совсем очевидно, как Divide переходит в Conquer. Нужно постараться, чтобы граф был более информативен и без чтения остальных разделов.&lt;/div&gt;</summary>
		<author><name>IgorS</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:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9/Zavolskov/Zemlyanskiy&amp;diff=18693</id>
		<title>Участник:Роман Землянский/Zavolskov/Zemlyanskiy</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:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9/Zavolskov/Zemlyanskiy&amp;diff=18693"/>
		<updated>2016-11-21T09:13:19Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
&#039;&#039;&#039;Завольсков Владислав&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[#Вычислительное ядро алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Макроструктура алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Схема реализации последовательного алгоритма]]&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;
[[#Последовательная сложность алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Ресурс параллелизма алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Входные и выходные данные алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Свойства алгоритма]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
614 группа&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - это наиболее быстрый из существующих методов, если нужны все собственные значения и собственные векторы трехдиагональной матрицы, начиная с порядка n, примерно равного 26. (Точное значение этого порогового порядка зависит от компьютера.) Его численно устойчивая реализация весьма не тривиальна. В самом деле, хотя впервые метод был предложен еще в 1981 г., &amp;quot;правильный&amp;quot; способ его реализации был найден лишь в 1992 г. Этот способ воплощен LAPACK-программами ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В них стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка, большего чем 25. Для матриц меньшего порядка (или если нужны только собственные значения) происходит автоматический переход к QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Предположим, что нам известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt; T_{2} &amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt; T_{i} = Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. В действительности, они будут рекурсивно вычисляться тем же самым алгоритмом. Установим связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Имеем:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} \Lambda_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
  &amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;&lt;br /&gt;
u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v&lt;br /&gt;
  &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;, получим матрицу, состоящую из последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt; и первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет те же собственные значения, что и подобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} \Lambda_{1} &amp;amp; 0 \\ 0 &amp;amp; \Lambda_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
&lt;br /&gt;
Будем предполагать, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по убыванию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, вычислим её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Поскольку &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденна, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - собственное значение. Заметим, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной добавлением матрицы ранга 1. Определитель такой матрицы легко вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;&#039;Лемма 1.&#039;&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Таким образом,&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; есть корни так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа показанного на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Graphics.PNG|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Можно видеть, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для этого графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Поскольку &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Поэтому корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень находится слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который быстро и монотонно сходится к каждому из корней, если начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать, что на практике метод сходится к каждому собственному значению за ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, а для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; требуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;&#039;Лемма 2.&#039;&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = (D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=u + \alpha(D - \alpha I)^{-1}u - u&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
                                                       поскольку &amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=\alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, что и требовалось.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов требуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, формула не обеспечивает численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Снова детали будут обсуждаться позднее в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
=== Дефляция ===&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это не так, вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а потому &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;дефляции&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, поэтому при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; посредством левого умножения &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не требует никаких затрат. Аналогичное упрощение имеет место в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, связанной с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения является вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов путём умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; Данная операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём и говорится в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
Ей предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Ниже описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
[[File:DivideAndConquerTree.png|thumb|center|left|800px|Рис. 2. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
[[File:InfoGraph.jpg|thumb|center|1200px|Рис. 3. Детальное описание одного блока алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Решение векового уравнения ===&lt;br /&gt;
Подробно стоит поговорить о решении векового уравнения, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
 1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 4. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.4 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:GraphNewton.png|thumb|center|800px|Рис. 4. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
 &amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
 &amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
 &amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (1)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (1) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Удовлетворяла условиям&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (2)&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;\equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&#039;&#039;Вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы посредством стратегии &amp;quot;разделяй и властвуй&amp;quot;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Если &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  1. Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Проанализируем сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов при обработке матрицы размера &amp;lt;math&amp;gt; n x n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. Тогда &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматриваются как плотные матрицы и используется стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке равна 1. Таким образом, именно это умножение составляет наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решая это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно гораздо меньше 1, потому что матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, называемого дефляцией.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
В данном разделе представлен информационный граф алгоритма: на рисунке 5 изображена часть алгоритма, в которой происходит деление - &amp;quot;Divide&amp;quot;, а на рисунке 6 описана часть &amp;quot;Conquer&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[[File:Infograph1.jpg|thumb|center|left|800px|Рис. 5. Информационный граф алгоритма &amp;quot;Разделяй и властвуй&amp;quot; - шаг &amp;lt;b&amp;gt;Divide&amp;lt;/b&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
[[File:Infograph2.jpg|thumb|center|1200px|Рис. 6. Информационный граф алгоритма &amp;quot;Разделяй и властвуй&amp;quot; - шаг &amp;lt;b&amp;gt;Conquer&amp;lt;/b&amp;gt;. Qk*k - матрица собственных значений размерности &amp;lt;b&amp;gt;k&amp;lt;/b&amp;gt;. Lk*k - матрица собственных векторов размерности &amp;lt;b&amp;gt;k&amp;lt;/b&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Операции: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - операция Divide разделения матрицы &amp;lt;math&amp;gt; T &amp;lt;/math&amp;gt; на матрицы &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Индексы - размерность &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_{k}&amp;lt;/math&amp;gt; - операция Conquer сбора матрицы &amp;lt;math&amp;gt; D &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; L_{k} &amp;lt;/math&amp;gt; - операция поиска собственных значений &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S^{&#039;} &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Рассмотрев информационный граф алгоритма, можно заметить, что в структуре имеется два параллельных блока - вызов рекурсивных функций &#039;&#039;dc_eig&#039;&#039; для вычисления собственных значений и векторов матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt; - это единственная часть алгоритма, в которой мы прибегаем к параллелизму. &lt;br /&gt;
&lt;br /&gt;
Рекурсивное разделение матрицы ведёт к иерархии вычислительных подблоков с графом зависимости данных, который выглядит, как двоичное дерево. Данная структура является естественным путем разделения вычислений между процессорами.&lt;br /&gt;
&lt;br /&gt;
В вершине дерева все процессы взаимодействуют. В каждой ветви дерева вычисление делится на два вычислительных подблока, внутри каждого из которых процессы параллельно взаимодействуют. В листьях деревьев каждый процесс занимается вычислением независимо.&lt;br /&gt;
&lt;br /&gt;
В случае реализации без параллелизма, функции &#039;&#039;dc_eig&#039;&#039; отрабатывают последовательно - сначала со входными параметрами &amp;lt;math&amp;gt;T_{1}, Q_{1}, \Lambda_{1}&amp;lt;/math&amp;gt;, затем - &amp;lt;math&amp;gt;T_{2}, Q_{2}, \Lambda_{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, благодаря параллелизму высота алгоритма параллельного составляет &amp;lt;math&amp;gt;log_{2}(n)&amp;lt;/math&amp;gt;, когда как при последовательной реализации высота алгоритма равна сумме геометрической прогрессии &amp;lt;math&amp;gt;1+2+4+8+...+2^{n-1} = 2^{n} - 1&amp;lt;/math&amp;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;
&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объем входных данных:&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt; - в силу симметрии, хранить следует только диагональные, поддиагональые и наддиагональные элементы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
На выходе мы получаем вектор собственных значений длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; собственных векторов длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; , соответствующих собственным значениям исходной матрицы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объем выходных данных:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; n + n^{2} = n(n + 1) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Описанный в данной статье алгоритм является самым быстрым алгоритмом, среди существующих: QR / Бисекция и обратная итерация / Разделяй и властвуй.&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма: &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Параллельная сложность алгоритма (в силу использования дефляции): &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;  или в самых редких случаях &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt; (см. [3] стр. 8).&lt;br /&gt;
&lt;br /&gt;
Особенности алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. Использование дефляции ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Использование адаптированного метода Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&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;quot;Ломоносов&amp;quot; Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 8] (1,2,3,4,8);&lt;br /&gt;
*	размер матрицы [4 : 8192].&lt;br /&gt;
&lt;br /&gt;
[[File:ResultsDiagram.jpg|thumb|center|left|800px|Рис. 7. График времени работы программы в зависимости от числа процессов и размера задачи]]&lt;br /&gt;
&lt;br /&gt;
Масштабируемость рассматриваемой реализации алгоритма &#039;&#039;&#039;&amp;quot;Разделяй и властвуй&amp;quot;&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* При увеличении числа процессов общая эффективность программы увеличивается, т.к. время чтения и подготовки исходной матрицы остаётся неизменным, а время работы алгоритма уменьшается, однако рост незначительный.&lt;br /&gt;
* При увеличении размерности матрицы общая эффективность программы постепенно уменьшается, т.к. время чтения и подготовки исходных данных растёт быстрее, чем время работы исследуемого алгоритма.&lt;br /&gt;
* При одновременном увеличении как числа процессов, так и размерности матрицы общая эффективность программы уменьшается.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Описываемый метод реализован в пакете LAPACK, описание которого можно найти в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf этой статье] на страницах 7, 19 и 20, которая указана в списке литературы.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Дж. Деммель, «Вычислительная линейная алгебра» //С. 230-235&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.netlib.org/utk/people/JackDongarra/PAPERS/104_1999_a-parallel-divide-and-conquer-algorithm.pdf Francoise Tisseury, Jack Dongarra, A Parallel Divide and Conquer Algorithm for the Symmetric Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[3] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[4] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[5] [http://www.cscamm.umd.edu/tadmor/pub/linear-stability/Gill_Tadmor_SISC90.pdf Doron Grill and Eitan Tadmor AN &amp;lt;math&amp;gt;O(N2)&amp;lt;/math&amp;gt; METHOD FOR COMPUTING THE EIGENSYSTEM OF &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;x&amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; SYMMETRIC TRIDIAGONAL MATRICES BY THE DIVIDE AND CONQUER APPROACH]&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Kanast74&amp;diff=18118</id>
		<title>Обсуждение участника:Kanast74</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Kanast74&amp;diff=18118"/>
		<updated>2016-11-18T11:41:43Z</updated>

		<summary type="html">&lt;p&gt;IgorS: /* Статья Алгоритм кластеризации, основанный на минимальном покрывающем дереве */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Статья [[Алгоритм кластеризации, основанный на минимальном покрывающем дереве]] =&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4 дополнить текстом исследуемой программы--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:05, 16 ноября 2016 (MSK)&lt;br /&gt;
* следует добавить подробное описание условий, в которых производились эксперименты: характеристики программно-аппаратной платформы (какой компилятор, с какими опциями использовался, какие версии библиотек, на каких узлах проводился запуск и т.д.)--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 18 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=18117</id>
		<title>Участник:Kanast74/Алгоритм кластеризации, основанный на минимальном покрывающем дереве</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=18117"/>
		<updated>2016-11-18T11:41:10Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Статью подготовила: [[Участник:kanast74|Дмитриева Анастасия Владимировна]].&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&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;X=\{x_1...x_n\}&amp;lt;/math&amp;gt;, а также функция расстояния между ними &amp;lt;math&amp;gt;r(e)&amp;lt;/math&amp;gt;. Отметим, что метрика, в которой вычисляются расстояния между объектами, выбирается исходя из требований задачи; наиболее распространенные - это Евклидово расстояние, квадрат Евклидова расстояния, манхэттенская метрика, расстояние Чебышева и степенное расстояние. Далее необходимо применить один из алгоритмов кластеризации, которые можно разделить на две категории: иерархические, состоящие в последовательном поиске кластеров из уже найденных кластеров, а также неиерархические, цель которых состоит в минимизации некоторой целевой функции. К последней группе относятся такие алгоритмы, как алгоритм k-средних, алгоритм EM, нечеткие алгоритмы, а также алгоритмы теории графов, к категории которых относится алгоритм, описываемый в данной статье.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим алгоритм кластеризации на k кластеров, основанный на построении минимального остовного дерева (MST) и который состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
# По исходным данным строится взвешенный неориентированный граф: каждый объект представляется в виде вершины графа, а вес ребра, связывающего две вершины, есть расстояние между объектами в выбранной заранее метрике&lt;br /&gt;
# Для полученного графа строится минимальное остовное дерево, то есть дерево, содержащее все вершины графа и имеющее минимальный суммарный вес своих ребер&lt;br /&gt;
# Удаляется k-1 ребро минимального остовного дерева с максимальным весом&lt;br /&gt;
# Получаются k компонент связности, которые интерпретируются как кластеры&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;nowiki&amp;gt;, наиболее подробно изучить который можно, пройдя по ссылке [&amp;lt;/nowiki&amp;gt;[https://algowiki-project.org/ru/Алгоритм_Крускала Алгоритм Крускала]], поэтому в данной статье его описание не приводится.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
С математической точки зрения задачу кластеризации можно рассматривать как минимизацию среднего внутрикластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} 1} \to min &amp;lt;/math&amp;gt; с одной стороны и максимизации межкластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)\neq c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i) \neq c(x_j)} 1} \to max &amp;lt;/math&amp;gt; с другой стороны, где &amp;lt;math&amp;gt;c(x)&amp;lt;/math&amp;gt; - кластер, к которому принадлежит объект &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Применительно к алгоритмам кластеризации, основанным на теории графов, процедура минимизации/максимизации соответствующих расстояний состоит в выборе определенного порогового значения веса ребра &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;, ребра с весом меньше которого предполагаются связующими для вершин, принадлежащих одному классу, а ребра с весом больше которого предполагаются связующими для вершин, принадлежащих разным классам.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром описываемого алгоритма является построение минимального остовного дерева. В случае, если программа предполагается быть распараллеленной, то операция построения MST будет производиться в отдельности в каждом процессе. Как уже было отмечено, в данном исследовании для построения MST использовался алгоритм Крускала, который будет работать наиболее эффективно, если веса ребер исходного графа отсортированы, поэтому сортировка - это еще одна операция, которая должна быть включена в вычислительное ядро, что предполагает выбранная ревлизация алгоритма. Поэтому в дальнейшем мы не будем разделять сортировку и поиск минимального остовного дерева, предполагая, что они объеденены в единый эффективный алгоритм. &lt;br /&gt;
&lt;br /&gt;
Дадим некоторую справку касательно адгоритма поиска MST, фигурирующего в данном исследовании. Алгоритм Крускала — алгоритм построения минимального остовного дерева взвешенного связного неориентированного графа. Алгоритм впервые описан Джозефом Крускалом в 1956 году.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Крускала находит безопасное ребро для добавления в растущий лес путем поиска ребра &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; с минимальным весом среди всех ребер, соединяющих два дерева в лесу. Обозначим два дерева, соединяемые ребром &amp;lt;math&amp;gt;(u, v) &amp;lt;/math&amp;gt;, как &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; — безопасное для &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; ребро. Алгоритм Крускала является жадным, поскольку на каждом шаге он добавляет к лесу ребро с минимально возможным весом.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
# Построение графа путем задания списков ребер. В общем случае, когда задано некоторое множество объектов, для построения графа может быть использован алгоритм, позволяющий рассчитать расстояние от каждого объекта до каждого за &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; число объектов (вообще говоря, данная операция требует &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; - число ребер в графе, но в нашем случае число ребер подразумевается равным &amp;lt;math&amp;gt;E=\frac{D (n(n-1))}{2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - плотность графа). Однако в рамках данного исследования для тестирования алгоритма использовалась такая технология построения графов, когда в качестве веса каждого ребра назначается некоторое произвольное число из заданного промежутка (например, от 1 до 1000), что также требует &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
# Построение минимального остовного дерева на основании полученного ранее графа, для чего может быть использован один из известных и наиболее распространенных алгоритмов (алгоритм Крускала, алгоритм Борувки или алгоритм Прима). Рассмотрим, например, алгоритм Крускала. На начальном этапе имеется граф, состоящий из вершин - объектов исходной выборки и не содержащий ребер, следовательно, каждая из вершин изначально представляет собой компоненту связности и даже дерево. Алгоритм состоит в выборе на каждом шаге ребра с минимальным весом и добавлении его в дерево, если оно соединяет два дерева. Алгоритм будет работать максимально эффективно, если изначально ребра графа, полученного на первом шаге будут отсортированы (например, по возрастанию). &lt;br /&gt;
# Для получения &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров необходимо удалить из получившегося минимального остовного дерева &amp;lt;math&amp;gt;k-1&amp;lt;/math&amp;gt; ребро с максимальным весом. В частности, в случае, если на предыдущем шаге мы пользовались алгоритмом Крускала для построения MST, ребра получившегося дерева отсортированы по возрастанию, значит для удаления указанных выше ребер потребуется лишь &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
# Наконец, необходимо правильно интерпретировать результат работы алгоритма и перечислить вершины, принадлежащие полученным кластерам; на выполнение данного шага может потребоваться различное число операций в зависимости от формата выходных данных программы: например, в данной реализации выходные данные, как будет сказано в дальнейшем, представлены в виде списка ребер, формирующий итоговый минимальный остовный лес, следовательно в общем случае на его обработку потребуется &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
==== Используемые обозначения и структуры данных ====&lt;br /&gt;
&lt;br /&gt;
Обозначим в качестве &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; граф, содержащий &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; вершин и &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; ребер. Для построения минимального остовного дерева для данного графа используются три операции [2], опишем здесь каждую из них:&lt;br /&gt;
&lt;br /&gt;
# операция MakeSet(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) генерирует дерево (множество) для вершины графа &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, являющейся корнем искомого дерева&lt;br /&gt;
# операция Find(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) определяет, которому из деревьев принадлежит интересующая вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Условимся, что данная операция возвращает ту вершину, которая является корнем искомого дерева&lt;br /&gt;
# операция Union(&amp;lt;math&amp;gt;u, v&amp;lt;/math&amp;gt;) объединяет два множества (два дерева, содержащих соответствующие вершины) под одним корнем&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма === &lt;br /&gt;
&lt;br /&gt;
Приведем здесь псевдокод основной части алгоритма - а именно построения локального минимального остовного дерева при помощи алгоритма Крускала.&lt;br /&gt;
&lt;br /&gt;
Kruskal ( G=(V,E) ):    &lt;br /&gt;
&lt;br /&gt;
1. mst = ∅;    &lt;br /&gt;
&lt;br /&gt;
2. сортировать E по возрастанию;&lt;br /&gt;
&lt;br /&gt;
3. для каждой вершины v из множества вершин V:&lt;br /&gt;
&lt;br /&gt;
4. MakeSet(v);&lt;br /&gt;
&lt;br /&gt;
5. для каждого ребра e = (u, v) из множества ребер E:&lt;br /&gt;
&lt;br /&gt;
6.    if Find(u) ≠ Find(v):&lt;br /&gt;
&lt;br /&gt;
7.    mst = mst ∪ { (u,v) }; &lt;br /&gt;
&lt;br /&gt;
8.    Union(u, v); &lt;br /&gt;
&lt;br /&gt;
9. return mst;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Сперва оценим последовательную сложность составных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
0. O(E): Формирование графа&lt;br /&gt;
&lt;br /&gt;
1. O(1): Инициализация пустого множества. &lt;br /&gt;
&lt;br /&gt;
2. O(Elog(E)): сортировка ребер из множества E. &lt;br /&gt;
&lt;br /&gt;
3. O(V): Инициализация одноточечного множества для каждой вершины из V. &lt;br /&gt;
&lt;br /&gt;
4.    ▲ MakeSet за O(1). &lt;br /&gt;
&lt;br /&gt;
5. O(Elog(V)): Операции Union и Find для каждого ребра из E. &lt;br /&gt;
&lt;br /&gt;
6.    ▲ Find за O(1). &lt;br /&gt;
&lt;br /&gt;
7.    ▲ Присоединение элемента за O(1). &lt;br /&gt;
&lt;br /&gt;
8.    ▲ Union за O(log(V)).&lt;br /&gt;
&lt;br /&gt;
9. O(1): Удаление k-1 ребра для получения k компонент связности&lt;br /&gt;
&lt;br /&gt;
10. O(V): Перечисление вершин, входящих в соответствующие кластеры&lt;br /&gt;
&lt;br /&gt;
11. O(1): Return.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учетом того, что, получаем:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T( G = (V, E) ) = O(1) + O(E\log(E)) + O(V) + O(E\log(V)) + O(E) = O(E\log(E)) + O(E\log(V))  = O(E\log(V)) + O(E\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Итак, оценка сложности алгоритма есть &amp;lt;math&amp;gt;T( G = (V, E) ) = O(E\log(V)) + O(\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
[[file:Clustering kruskal.png|thumb|center|1400px|Рисунок 1. Информационный граф алгоритма]]&lt;br /&gt;
&lt;br /&gt;
По структуре информационного графа можно сделать вывод что высота канонической ярусно-параллельной формы [1] есть &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; число процессов.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Описанный алгоритм позволяет произвести распараллеливание, источник которого будет описан в данном пункте. Прежде всего, необходимо отметить, что данный алгоритм допускает преобразование в гибридную параллельную версию, а именно версию с использованием технологий MPI и OpenMP. &lt;br /&gt;
&lt;br /&gt;
Тем не менее, прежде всего следует установить справедливость свойства используемого алгоритма поиска MST, позволяющего произвести его распараллеливание [3]: &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ассоциативность по рёбрам&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;MSF(E)&amp;lt;/math&amp;gt; – минимальный остовный лес графа с рёбрами &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;. Тогда&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        MSF(E_1 \cup E_2 \cup \dots \cup E_k) = MSF(MSF(E_1) \cup MSF(E_2) \cup \dots \cup MSF(E_k)).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В том случае, когда все объекты заранее известны, и между ними можно рассчитать все попарные расстояния, первый шаг - построение графа - позволяет произвести распараллеливание при помощи обеих вышеуказанных технологий, а именно: разделить объекты между процессами при помощи MPI, а внутри каждой из полученных групп будем рассчитывать попарные расстояния параллельно на нескольких ядрах, использовав технологию OpenMP. Однако в данном исследовании кластеризуемые объекты предполагались заранее неизвестными, так как попарные расстояния между объектами задаются случайным образом, а это означает, что произвести распараллеливание этого шага, которое бы дало хотя бы небольшое ускорение (уменьшение времени работы), не получится.&lt;br /&gt;
&lt;br /&gt;
Если все же объекты заранее были известны, можно сохранить полученную на предыдущем этапе группировку их на отдельные процессы и внутри каждого произвести сортировку расстояний между объектами по возрастанию при помощи алгоритма QSort с использованием технологии OpenMP (в противном случае параллельная структура алгоритма поддерживается начиная с этого шага).&lt;br /&gt;
&lt;br /&gt;
Далее внутри каждой группы объектов (каждый процесс в отдельности) строим минимальное остовное дерево, в конце шага деревья объединяются в одно - общее MST для всех объектов, поддерживая при этом отсортированность расстояний по возрастанию.&lt;br /&gt;
&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  &amp;lt;math&amp;gt; \log{p} &amp;lt;/math&amp;gt; шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
==== Способы разделения графа ====&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;G = (V, E)&amp;lt;/math&amp;gt;, являющийся неориентированным, взвешенным, связным и не имеющим кратных ребер. Пусть полученный граф содержит &amp;lt;math&amp;gt; |V|&amp;lt;/math&amp;gt; вершин и  &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; ребер. Предполагая, что каждый объект можно сравнить с каждым, можно считать, что получаемый граф является полным, а значит число ребер может быть оценено следующим образом: &amp;lt;math&amp;gt;|E|=\frac{|V|(|V|-1)}{2} \le \frac{|V|^2}{2}-\frac{|V|}{2} \Rightarrow O(E)=O(V^2)&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Обозначим в виде &amp;lt;math&amp;gt;w(u,v)&amp;lt;/math&amp;gt; вес ребра, соединяющего вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Тогда граф может быть задан в виде списка ребер, а именно списка троек: &amp;lt;math&amp;gt;(u,v,w(u,v))&amp;lt;/math&amp;gt;, что является более удобным представлением графа для реализации многих алгоритмов по сравнению с матрицей смежности. Помимо этого, данный способ помогает сэкономить память. Недостатков указанного подхода является только то, что установить, с какими вершинами связана интересующая вершина, а также вес соответствующих ребер является весьма затруднительно. Однако для решения поставленной задачи нам это и не потребуется.&lt;br /&gt;
&lt;br /&gt;
Формат выходных данных аналогичный&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
==== Вычислительная мощность ====&lt;br /&gt;
Оценим вычислительную мощность алгоритма в том случае, когда для его реализации используется &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов. Предположим, что алгоритм реализован при помощи первой стратегии (см. выше) и применяется к графу &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt;, тогда:&lt;br /&gt;
&lt;br /&gt;
* Ввод данных: &amp;lt;math&amp;gt;\frac{|E|}{p}&amp;lt;/math&amp;gt; - в среднем столько ребер содержит получаемый каждым процессом связный подграф&lt;br /&gt;
* Вывод данных: &amp;lt;math&amp;gt;\frac{|V| - 1}{p}&amp;lt;/math&amp;gt; - каждый из процессов должен вывести посчитанное локальное минимальное дерево, которое содержит указанное количество ребер, так как остальные ребра, изначально входившие в состав подграфа, не будут входит в локальное MST&lt;br /&gt;
* Вычисление: для вычисления локального MST каждому из процессов требуется &amp;lt;math&amp;gt;\frac{|E|}{p} \log{\frac{|V|}{p}}&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
Таким образом, искомое соотношение может быть записано в следующем виде: &amp;lt;math&amp;gt;\frac{\frac{|E|}{p} \log{\frac{|V|}{p}}}{\frac{|E|}{p} + \frac{|V| - 1}{p}} = \frac{|E| \log{\frac{|V|}{p}}}{|E|+|V|-1}&amp;lt;/math&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;
В данном рзеделе будет производиться оценка прироста производительности параллельного алгоритма по сравнению с последовательным. Наиболее простой способ оценить прирост производительности – использовать закон Амдала, который гласит, что прирост может быть оценен следующим образом: &amp;lt;math&amp;gt; S_p \le \frac{1}{f+\frac{1-f}{p}}&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; – часть алгоритма, которая не может быть распараллелена, &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; – число процессов.&lt;br /&gt;
&lt;br /&gt;
В наиболее тривиальном случае (первая стратегия распараллеливания) распараллеливанию не подлежит считывание входных данных и их разделение между процессами, а также объединение локальных MST в одно. Поэтому сложность последовательной части программы может быть оценена следующим образом: &amp;lt;math&amp;gt;2O(V^2)+O(V^2 \log{V})&amp;lt;/math&amp;gt;. Распараллеливанию подлежит инициализация подграфов при помощи операции MakeSet за &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt;, сортировка ребер по возрастанию внутри каждой группы за &amp;lt;math&amp;gt;O(V^2 \log{V})&amp;lt;/math&amp;gt;, а также построение локальных минимальных остовных деревьев за аналогичное время. Поэтому сложность параллельной части алгоритма может быть оценена так:  &amp;lt;math&amp;gt;O(V)+2 O(V^2 \log{V})&amp;lt;/math&amp;gt;. Таким образом, вычислив отношение сложностей частей программы, получаем, что  &amp;lt;math&amp;gt;f=0.44&amp;lt;/math&amp;gt;. Устремим число процессов  &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; к бесконечности и получим, что максимальное ускорение, которое можно получить путем распараллеливания алгоритма есть &amp;lt;math&amp;gt;2.27&amp;lt;/math&amp;gt;. К сожалению, данная оценка не является точной. Для того, чтобы оценить прирост производительности более качественно, произведем анализ временных диаграмм программы.&lt;br /&gt;
&lt;br /&gt;
===== Анализ временных диаграмм =====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;t_d&amp;lt;/math&amp;gt; – время, которое требуется для того, чтобы разделить граф на подграфы, &amp;lt;math&amp;gt;t_p&amp;lt;/math&amp;gt; – суммарное время вычислений, &amp;lt;math&amp;gt;t_b&amp;lt;/math&amp;gt; – время обратной пересылки локально полученных результатов, &amp;lt;math&amp;gt;t_l&amp;lt;/math&amp;gt; – задержка одной пересылки. Причем можно убедиться в том, что &amp;lt;math&amp;gt;t_p &amp;gt; t_d &amp;gt; t_b &amp;gt; t_l&amp;lt;/math&amp;gt;, также известно, что время выполнения последовательного кода &amp;lt;math&amp;gt;t_{serial} = t_p&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:FirstStrategy.PNG|thumb|center|400px|Рисунок 2. Первая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Произведем анализ первой (наивной) стратегии распараллеливания программы. На временной диаграмме жирным обозначен критический путь, время выполнения которого может быть вычислено по формуле (см: [2]):&lt;br /&gt;
&amp;lt;math&amp;gt;t_{paralel} = 4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда максимальное ускорение для одного процесса есть: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{t_p}{4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А в случае n процессов: &amp;lt;math&amp;gt;speedup = \frac{n}{1+\frac{n (n+1) t_l + n t_d + t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:SecondStrategy.PNG|thumb|center|400px|Рисунок 3. Вторая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Теперь проанализируем вторую стратегию, основная задача которой – уменьшить объем коммуникаций между процессами за счет того, что файл с исходными данными теперь доступен всем процессам. Помимо этого введем в рассмотрение новую величину &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; – время, требующееся на инициализацию множеств вершин. Тогда для одного процесса &amp;lt;math&amp;gt;t_{parallel} = t_i + \frac{t_p}{4} + t_l + 3 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а максимальное ускорение в случае n процессов есть: &amp;lt;math&amp;gt;speedup= \frac{t_{serial}}{t_{parallel}} = \frac{4}{1+\frac{4 t_i + 4 t_l + 3 t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:ThirdStrategy.PNG|thumb|center|400px|Рисунок 4. Третья стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Наконец, проанализируем третью стратегию, основная цель которой – оптимизировать процесс слияния локальных минимальных остовных деревьев, а также привлечь все активные процессы (включая главный) к процессу построения локального MST. В этом случае время выполнения параллельной части программы с использованием лишь одного процесса: &amp;lt;math&amp;gt; t_{parallel}=t_i + \frac{t_p}{4} + t_l + 2 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а суммарное ускорение в общем случае: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{n}{1+\frac{n t_i + n t_l + \log_2{n} t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На рисунке 5 приведены графики, отражающие зависимость получаемых возможных ускорений в зависимости от числа задействованных процессов, посчитанные с использованием четырех различных подходов.&lt;br /&gt;
[[Файл:Comparison.PNG|thumb|center|400px|Рисунок 5. Сравнение стратегий распараллеливания]]&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
[[file:Total.png|thumb|center|400px|Рисунок 6. График зависимости времени вычисления от размера задачи и числа процессов (с учетом времени ввода-вывода)]]&lt;br /&gt;
[[file:Wo_io.png|thumb|center|400px|Рисунок 7. График зависимости времени вычисления от размера задачи и числа процессов (без учета времени ввода-вывода)]]&lt;br /&gt;
&lt;br /&gt;
На графиках видно, как с увеличением объема вычислительных ресурсов происходит прирост в производительности. Данный эффект происходит до некоторых пор, после чего накладные расходы на коммуникацию между процессами нивелируют его. При дальнейшем увеличении числа процессов время решения задачи заметно увеличивается, однако, ввиду длинных очередей на суперкомпьютере Ломоносов данные с числом процессов &amp;lt;math&amp;gt; \ge 256&amp;lt;/math&amp;gt; получить не удалось&lt;br /&gt;
&lt;br /&gt;
Исходный код, запускавшийся на суперкомпьютере Ломоносов-1:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Source codes.docx]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма  [3]===&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/kruskal_min_spanning_tree.html kruskal_min_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#merge-local-minimum-spanning-trees merge_local_minimum_spanning_trees]&amp;lt;/code&amp;gt; реализует алгоритм Крускала;&lt;br /&gt;
** функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают [[алгоритм Борувки]] и алгоритм Крускала.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.mst.minimum_spanning_tree.html minimum_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/KruskalMinimumSpanningTree.html KruskalMinimumSpanningTree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. - СПб.: БХВ-Петербург, 2002. - 608 с.&lt;br /&gt;
&lt;br /&gt;
[2] http://www.gpiskas.com/pdf/kruskal_mpi.pdf (дата обращения: 02.10.2016)&lt;br /&gt;
&lt;br /&gt;
[3] https://algowiki-project.org/ru/Алгоритм_Крускала&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Vagabond&amp;diff=18116</id>
		<title>Обсуждение участника:Vagabond</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Vagabond&amp;diff=18116"/>
		<updated>2016-11-18T11:40:03Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Замечания к статье ===&lt;br /&gt;
&lt;br /&gt;
* содержание не соответствуют шаблону описания алгоритма: неверная нумерация, отсутствуют п.п.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:30, 25 октября 2016 (MSK)   &lt;br /&gt;
* не указан конкретный вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:30, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 1.9 не раскрыт--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:30, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 2.4 дополнить кодом исследуемой программы--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:52, 16 ноября 2016 (MSK)&lt;br /&gt;
* следует добавить подробное описание условия, в которых производились эксперименты: характеристики программно-аппаратной платформы (какой компилятор, с какими опциями использовался, какие версии библиотек, на каких узлах проводился запуск и т.д.)--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:40, 18 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Алгоритм быстрого дискретного преобразования Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* Раздел 1.1 - вместо общего описания алгоритма написано именно что математическое описание. В общем описании должна быть поставлена задача и изложена основная идея, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 14:08, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2 - не очень ясны слова про &amp;quot;проиллюстрированные на рисунке 2 в разделе Информационный граф&amp;quot;. Раздел &amp;quot;Информационный граф&amp;quot; содержит рисунок 1. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 14:08, 11 ноября 2016 (MSK)&lt;br /&gt;
* Если упоминаем рекурсию, нужны слова о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 14:08, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=18115</id>
		<title>Участник:Vagabond/Алгоритм быстрого дискретного пребразования Фурье</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=18115"/>
		<updated>2016-11-18T11:38:44Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&#039;&#039;&#039;Автор&#039;&#039;&#039;: Данилишин А.Р.&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;X=(x_{1},...,x_{n-1})^{T}&amp;lt;/math&amp;gt; - исходный сигнал, который необходимо преобразовать умножением на тензор второго ранга &amp;lt;math&amp;gt;E_{pk}=e^{\frac{-2kpi\pi}{n}} &amp;lt;/math&amp;gt;. Результатом данного преобразования (&amp;lt;math&amp;gt;E*X&amp;lt;/math&amp;gt;) будет вектор &amp;lt;math&amp;gt;Y=(y_{1},...,y_{n-1})^{T}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2kpi\pi}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. Данная формула является классической формулой ДПФ. Известно, что в общем случае умножение матрицы на вектор требует &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; арифметических операций, поэтому возникает вопрос о сокращении данного количества.&lt;br /&gt;
&lt;br /&gt;
Главная идея БПФ состоит в сокращении многочисленных повторов сложения и умножения, которые выполняются в ДПФ (повторяющиеся множители: &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; Например при условии, что  &amp;lt;math&amp;gt;(pk)mod(n)=const&amp;lt;/math&amp;gt;, при всех &amp;lt;math&amp;gt;k=\overline{0,n-1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;p=\overline{0,n-1}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
За счет использования данного факта, происходит значительное сокращение количества вычислительных операций. В данной статье используется алгоритм Cooley – Tukey, который предполагает собой разбиение исходного множества входных сигналов на два равных подмножества. При этом множество входных сигналов должно быть степенью двойки. Далее операция деления множества продолжается для образовавшихся подмножеств, и так далее. В результате над каждым из полученных подмножеств выполняется БПФ. Данные вычисления можно выполнять независимо друг от друга. Этот факт используется для реализации параллельной версии алгоритма. &lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
*Пусть &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\omega _{n}^{kp}&amp;lt;/math&amp;gt;.&lt;br /&gt;
*Разобьем исходный сигнал &amp;lt;math&amp;gt;x(n)&amp;lt;/math&amp;gt; на два подмножества, с четными &amp;lt;math&amp;gt;x_{1}(2n)&amp;lt;/math&amp;gt; и нечетными &amp;lt;math&amp;gt;x_{2}(2n+1)&amp;lt;/math&amp;gt; номерами.&lt;br /&gt;
Тогда получаем: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}\omega _{n}^{kp}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{(2k+1)p}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выпишем два свойства омеги:&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{\frac{n}{2}}=e^{2(\frac{-2i\pi}{n})}=\omega_{n}^{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{n}^{\frac{n}{2}}=e^{\frac{-2ni\pi}{2n}}=e^{-i\pi}=-1&amp;lt;/math&amp;gt;&lt;br /&gt;
Воспользуемся первым: &amp;lt;math&amp;gt;y_{p}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{2kp}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;. Или, если обозначить &amp;lt;math&amp;gt;y_{1}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;y_{2}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, то получим формулу для вычисления координат изображения БПФ при &amp;lt;math&amp;gt;p=\overline{0...n/2}&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;y_(p)=y_{1}(p)+\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заметим, что &amp;lt;math&amp;gt;y(p); y_{1}(p); y_{2}(p)&amp;lt;/math&amp;gt; обладают свойством периодичности, с периодом &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt;. Покажем это для &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt;y_{1}(p+\frac{n}{2})=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{k(p+\frac{n}{2})}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}\omega_{\frac{n}{2}}^{\frac{kn}{2}}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как: &amp;lt;math&amp;gt;\omega_{\frac{n}{2}}^{\frac{kn}{2}}=e^{\frac{-4kni\pi}{2n}}=e^{-2ki\pi}=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Далее необходимо определить &amp;lt;math&amp;gt;y(p)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;p\geq \frac{n}{2}&amp;lt;/math&amp;gt;. Теперь воспользуемся свойством периодичности обнаруженными нами ранее: &amp;lt;math&amp;gt;y(p+\frac{n}{2})=y_{1}(p+\frac{n}{2})+\omega_{n}^{(p+\frac{n}{2})}y_{2}(p+\frac{n}{2})&amp;lt;/math&amp;gt;. Воспользуемся вторым свойством омеги: &amp;lt;math&amp;gt;y(p)=y_{1}(p)-\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;. Все выкладки, приведенные выше, относятся ко второму этапу алгоритма Cooley – Tukey, который был использован в данной статье. Последующие шаги данного этапа делят подмножества полученные на предыдущем шаге вычисления БПФ.&lt;br /&gt;
&lt;br /&gt;
В конечном итоге вышеупомянутый алгоритм сводится к двум этапам:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Бит-реверсирование&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Бит-реверсирование – это преобразование двоичного числа путем изменения порядка следования бит в нем на противоположный. Бит-реверсирование применимо только к индексам элементов массива и предназначено для изменения порядка следования этих элементов, при этом значения самих элементов не изменяются. Так, к примеру, четырехмерный вектор &amp;lt;math&amp;gt;(x_{0},x_{1},x_{2},x_{3})&amp;lt;/math&amp;gt; изменится на &amp;lt;math&amp;gt;(x_{0},x_{2},x_{1},x_{3})&amp;lt;/math&amp;gt;. Данная процедура необходима для разделения исходного сигнала на элементы подмножеств с четными и нечетными номерами, которые необходимы для вычисления &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Вычисление БПФ&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
После выполнения этапа бит-реверсирования выполняются вычисления проиллюстрированные &#039;&#039;на рисунке 1&#039;&#039; в разделе [[#Информационный граф|Информационный граф]]. Рисунок 1 приведен для случая четырехмерного входного сигнала. Он предполагает совершение двух этапов. В общем же количество этапов равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Каждый этап содержит в себе вычисление одной и той же операции &amp;quot;бабочки&amp;quot;, где на вход подаются два числа &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;, на выходе же получаются &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;- это &#039;&#039;коэффициент поворота&#039;&#039;. Общая формула для коэффициента поворота: &amp;lt;math&amp;gt;e^{\frac{-ji\pi}{k}}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;- размер шага &amp;quot;бабочки&amp;quot;, &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;- номер итерации вычислений над группой элементов, размером &amp;lt;math&amp;gt;2k&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;(0\leq j &amp;lt; k )&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из двух вышеописанных этапов БПФ, бит-реверсирование является самым быстровыполнимым. Поэтому вычислительное ядро алгоритма будет лежать именно во втором этапе. А именно, им будет являться вычисление &amp;quot;бабочек&amp;quot;. Количество этапов выполнения БПФ, как описывалось выше, равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Учитывая количество этапов, размерность входного сигнала и идею деления на подмножества, всего &amp;quot;бабочек&amp;quot; получается &amp;lt;math&amp;gt;\frac{nlog_{2}n}{2}&amp;lt;/math&amp;gt;. При этом операция умножения выполняется &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; раз. Это связано с тем, что результаты вычисления &amp;quot;бабочки&amp;quot; &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из всего вышесказанного заключаем, что макроструктуру алгоритма легче всего описать при помощи рекурсии: &lt;br /&gt;
*&amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; преобразований Фурье порядка 2&lt;br /&gt;
*умножение &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; пар комплексных чисел&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; комплексных сложений &lt;br /&gt;
*снова преобразование Фурье&lt;br /&gt;
Также необходимо заметить, что количеством рекурсий будет количество этапов выполнения БПФ, то есть глубина рекурсии будет равняться &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&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;quot;бабочка&amp;quot;&lt;br /&gt;
*выполнение БПФ&lt;br /&gt;
*функция вывода результата (для тестирования)&lt;br /&gt;
----&lt;br /&gt;
&#039;&#039;&#039;главный программный код БПФ:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; *inputSignal = NULL;   &lt;br /&gt;
	complex&amp;lt;double&amp;gt; *outputSignal = NULL; &lt;br /&gt;
	int size = 0;&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;FFT. Serial version.&amp;quot; &amp;lt;&amp;lt; endl;       &lt;br /&gt;
	ProcessInitialization(inputSignal, outputSignal, size);&lt;br /&gt;
	bit(inputSignal, outputSignal, size);&lt;br /&gt;
	SerialFFTCalculation(outputSignal, size);&lt;br /&gt;
	PrintSignal(outputSignal, size); //в случае тестирования&lt;br /&gt;
	ProcessTermination(inputSignal, outputSignal);&lt;br /&gt;
	cin.get();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
=== функция бит-реверсирования ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void bit(complex&amp;lt;double&amp;gt; inputSignal[], complex&amp;lt;double&amp;gt; *outputSignal, int size)&lt;br /&gt;
{ // процесс бит-реверсирования&lt;br /&gt;
	int j = 0, i = 0;&lt;br /&gt;
	while (i &amp;lt; size) &lt;br /&gt;
	{&lt;br /&gt;
		if (j &amp;gt; i) &lt;br /&gt;
		{ &lt;br /&gt;
			outputSignal[i] = inputSignal[j];       &lt;br /&gt;
			outputSignal[j] = inputSignal[i]; &lt;br /&gt;
		}&lt;br /&gt;
		else       &lt;br /&gt;
			if (j == i)          &lt;br /&gt;
				outputSignal[i] = inputSignal[i];&lt;br /&gt;
		&lt;br /&gt;
		int m = size &amp;gt;&amp;gt; 1;     &lt;br /&gt;
		while ((m &amp;gt;= 1) &amp;amp;&amp;amp; (j &amp;gt;= m)) &lt;br /&gt;
		{ &lt;br /&gt;
			j -= m;       &lt;br /&gt;
			m = m &amp;gt;&amp;gt; 1; &lt;br /&gt;
		}     &lt;br /&gt;
		j += m;     &lt;br /&gt;
		i++;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация размерности входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessInitialization(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal, int &amp;amp;size) &lt;br /&gt;
{&lt;br /&gt;
	// инициализация размера входного сигнала  &lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		cout &amp;lt;&amp;lt; &amp;quot;Enter the input signal length: &amp;quot;;&lt;br /&gt;
		cin &amp;gt;&amp;gt; size;&lt;br /&gt;
		if (size &amp;lt; 4)&lt;br /&gt;
			cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be &amp;gt;= 4&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
		else&lt;br /&gt;
		{&lt;br /&gt;
			int tmpSize = size;&lt;br /&gt;
			while (tmpSize != 1)&lt;br /&gt;
			{&lt;br /&gt;
				if (tmpSize % 2 != 0)&lt;br /&gt;
				{&lt;br /&gt;
					cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be powers of two&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
					size = -1;&lt;br /&gt;
					break;&lt;br /&gt;
				}&lt;br /&gt;
				tmpSize /= 2;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	while (size &amp;lt; 4);  &lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Input signal length = &amp;quot; &amp;lt;&amp;lt; size &amp;lt;&amp;lt; endl; &lt;br /&gt;
	inputSignal = new complex&amp;lt;double&amp;gt;[size];   &lt;br /&gt;
	outputSignal = new complex&amp;lt;double&amp;gt;[size];&lt;br /&gt;
	DummyDataInitialization(inputSignal, size);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void DummyDataInitialization(complex&amp;lt;double&amp;gt;* mas, int size)&lt;br /&gt;
{	&lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)&lt;br /&gt;
	{&lt;br /&gt;
		mas[i] = int(rand() % 100);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== процесс очистки памяти ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessTermination(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal) &lt;br /&gt;
{ &lt;br /&gt;
	//очистка памяти&lt;br /&gt;
	delete[] inputSignal;   &lt;br /&gt;
	inputSignal = NULL;   &lt;br /&gt;
	delete[] outputSignal;   &lt;br /&gt;
	outputSignal = NULL; &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение операции &amp;quot;бабочка&amp;quot; ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void Butterfly(complex&amp;lt;double&amp;gt; *signal, complex&amp;lt;double&amp;gt; &amp;amp;u,int offset, int butterflySize)&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; tem = signal[offset + butterflySize] * u;&lt;br /&gt;
	signal[offset + butterflySize] = signal[offset] - tem;   &lt;br /&gt;
	signal[offset] += tem;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение БПФ ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void SerialFFTCalculation(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	int m = 0;   &lt;br /&gt;
	for (int tmp_size = size; tmp_size &amp;gt; 1; tmp_size /= 2, m++);&lt;br /&gt;
	for (int p = 0; p &amp;lt; m; p++)&lt;br /&gt;
	{&lt;br /&gt;
		int butterflyOffset = 1 &amp;lt;&amp;lt; (p + 1);&lt;br /&gt;
		int butterflySize = butterflyOffset &amp;gt;&amp;gt; 1;&lt;br /&gt;
		double coeff = M_PI / butterflySize;&lt;br /&gt;
&lt;br /&gt;
		for (int i = 0; i &amp;lt; size / butterflyOffset; i++)&lt;br /&gt;
			for (int j = 0; j &amp;lt; butterflySize; j++)&lt;br /&gt;
				Butterfly(signal,complex&amp;lt;double&amp;gt;(cos(-j*coeff),sin(-j*coeff)),j+i*butterflyOffset,butterflySize);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== функция вывода результата (для тестирования) ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void PrintSignal(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Result signal&amp;quot; &amp;lt;&amp;lt; endl;   &lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)     &lt;br /&gt;
		cout &amp;lt;&amp;lt; signal[i] &amp;lt;&amp;lt; endl;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотренный алгоритм  Cooley – Tukey выполняется за &amp;lt;math&amp;gt;nlog_{2}n&amp;lt;/math&amp;gt; операций комплексного сложения и &amp;lt;math&amp;gt;\frac{n}{2}log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения. Таким образом последовательная сложность алгоритма является &#039;&#039;&#039;линейно-логарифмической&#039;&#039;&#039;.&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[file:Граф.jpg|center|thumb|800px|Рисунок 1. Алгоритм Cooley – Tukey для n=4, при предварительно сделанном бит-реверсировании]]&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
[[file:Парал.jpg|center|thumb|800px|Рисунок 2. Распараллеливание алгоритма Cooley – Tukey для n=8]]&lt;br /&gt;
Рисунок 2 получен из информационного графа, главной идеей которого является процедура деления множеств полученных на предыдущем шаге на два равных по мощности подмножества и применение к ним БПФ. То есть процесс вычисления распределяется между &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; процессорами. И так как при последовательной реализации алгоритма было &amp;lt;math&amp;gt;\frac{n}{2}\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;n\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. Получается, что при параллельной реализации алгоритма мы будем иметь &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;2\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. При этом, если используется функция HT(Hyper threading), то параллельная сложность быстрого преобразования Фурье (базовый алгоритм Кули-Тьюки) для векторов с длиной, равной степени двойки составляет &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt;.&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
*&#039;&#039;&#039;Вещественное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{R}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
*&#039;&#039;&#039;Комплексное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
*Соотношение последовательной и параллельной сложности является &#039;&#039;линейным&#039;&#039;. Это хорошо видно из пункта [[#Ресурс параллелизма алгоритма|ресурс параллелизма алгоритма]].&lt;br /&gt;
*Вычислительная мощность алгоритма является &#039;&#039;логарифмической&#039;&#039;.&lt;br /&gt;
*Устойчивость. &lt;br /&gt;
**Рассмотрим вопрос об устойчивости поближе. Так как БПФ отличается от ДПФ простым лишь сокращением одинаковых операций, то вопрос устойчивости можно рассматривать на примере ДПФ. Так, к примеру, возьмем формулу ДПФ и добавим возмущение: &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}(x_{k}+\varepsilon )e^{\frac{-2*\pi*k*p*i}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. После чего преобразуем ее: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2*\pi*k*p*i}{n}}+\varepsilon\sum_{k=0}^{n-1} e^{\frac{-2*\pi*k*p*i}{n}}&amp;lt;/math&amp;gt; и оценим вторую сумму, которая является геометрической прогрессией со знаменателем &amp;lt;math&amp;gt;q=e^{\frac{-2*\pi*p*i}{n}}&amp;lt;/math&amp;gt;. Таким образом оцениваемая сумма будет равняться: &amp;lt;math&amp;gt;\varepsilon\frac{1-e^{\frac{-2*\pi*p*i*(n-1))}{n}}}{1-e^{\frac{-2*\pi*p*i}{n}}}&amp;lt;/math&amp;gt;. При достаточно больших размерностях входного сигнала данное выражение сводится к: &amp;lt;math&amp;gt;\varepsilon (1-e^{-2*\pi*p*i})&amp;lt;/math&amp;gt;. В тригонометрической форме данного выражения: &amp;lt;math&amp;gt;\varepsilon (cos(\frac{2*\pi*p*(n-1)}{n})-i*sin(\frac{2*\pi*p*(n-1)}{n}))&amp;lt;/math&amp;gt; видно, что принимая во внимание целочисленность индексов &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; получается 0. Таким образом, мы можем говорить об устойчивости алгоритма в целом, и абсолютной устойчивости алгоритма в асимптотике.&lt;br /&gt;
*исходя из информационного графа, можно сделать вывод о сбалансированности алгоритма.&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:масш.jpg|center|thumb|1000px|Рисунок 3. Параллельная реализация алгоритма. Изменение эффективности(%)в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
Как описывалось выше, каждый процессор получает достаточно низкий объем работы, что приводит к неэффективному распараллеливанию алгоритма (высокие затраты на передачу данных между процессорами по сравнению с вычислениями на них). Поэтому необходимо находить оптимальное количество процессоров для каждой отдельно взятой задачи. В данном примере, при размерности входного сигнала менее 262144, оптимальное количество процессоров составляет 1. При размерности 262144-4194304 оптимум достигался при 8 процессорах. И при размерности более 4194304- 16 процессоров. Вычисления проводились на суперкомпьютере «Ломоносов». &lt;br /&gt;
Код исследуемой программы представлен в разделе [[#Существующие реализации алгоритма|Существующие реализации алгоритма]]&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Самой распространенной и известной реализацией дискретного БПФ является библиотека [http://www.fftw.org/ FFTW], разработанная Маттео Фриго и Стивеном Г. Джонсоном в Массачусетском технологическом институте. Данная библиотека существует для языков C и FORTRAN и позволяет производить вычисления в одном или нескольких измерениях. Входной сигнал может быть как действительным так и комплексным, а его размерность не обязательно должна быть степенью двойки. На данный момент последняя версия FFTW 3.3.5. Другие особенности библиотеки:&lt;br /&gt;
*Скорость. (Поддержка SSE / SSE2 / Altivec, начиная с версии 3.0. Версия 3.3.1 поддерживает AVX и ARM Neon.)&lt;br /&gt;
*Параллельные вычисления (OpenMP, MPI).&lt;br /&gt;
*[http://www.fftw.org/fftw3_doc/ Документация] в форматах HTML, PDF.&lt;br /&gt;
*Данное программное обеспечение является свободным и распространяется по лицензии GNU (General Public License).&lt;br /&gt;
Здесь приводится пример реализации прямого БПФ для комплексных данных входного сигнала на языке C++. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;fftw3-mpi.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/stat.h&amp;gt;&lt;br /&gt;
#include &amp;lt;fcntl.h&amp;gt;&lt;br /&gt;
# include &amp;lt;time.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
	const ptrdiff_t N0 = 4194304;&lt;br /&gt;
	fftw_plan planForw;&lt;br /&gt;
	fftw_complex *data, *dataOut;&lt;br /&gt;
	ptrdiff_t alloc_local, local_ni, local_i_start, i, j, local_no, local_o_start;&lt;br /&gt;
	int index, size;&lt;br /&gt;
	double startwtime, endwtime;&lt;br /&gt;
	MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
	fftw_mpi_init();&lt;br /&gt;
	MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;index);&lt;br /&gt;
	MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);&lt;br /&gt;
&lt;br /&gt;
	/* get local data size and allocate */&lt;br /&gt;
	alloc_local = fftw_mpi_local_size_1d(N0, MPI_COMM_WORLD, FFTW_FORWARD, FFTW_ESTIMATE,&lt;br /&gt;
		&amp;amp;local_ni, &amp;amp;local_i_start, &amp;amp;local_no, &amp;amp;local_o_start);&lt;br /&gt;
	data = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	dataOut = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	/* create plan  */&lt;br /&gt;
	planForw = fftw_mpi_plan_dft_1d(N0, data, dataOut, MPI_COMM_WORLD,&lt;br /&gt;
		FFTW_FORWARD, FFTW_ESTIMATE);&lt;br /&gt;
	/* initialize data to some function my_function(x,y) */&lt;br /&gt;
	for (i = 0; i &amp;lt; local_ni; ++i)&lt;br /&gt;
	{&lt;br /&gt;
		data[i][0] = rand() / (double)RAND_MAX;&lt;br /&gt;
		data[i][1] = rand() / (double)RAND_MAX;&lt;br /&gt;
	}&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		startwtime = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_execute(planForw);&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		endwtime = MPI_Wtime();&lt;br /&gt;
		printf(&amp;quot;wall clock time = %f\n&amp;quot;,&lt;br /&gt;
			endwtime - startwtime);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_destroy_plan(planForw);&lt;br /&gt;
	MPI_Finalize();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
[http://www.fftw.org/download.html Скачав] библиотеку и установив ее на рабочий компьютер, можно поэкспериментировать, используя вышеописанный исходный код. При этом, на стадии компиляции необходимо указывать директорию, где находятся заголовочные файлы и директорию, где находятся библиотечные файлы. Пример: mpicxx name.cpp -I/path/to/include/files -L/path/to/lib/files  -lfftw3_mpi -lfftw3 -lm. Для версии 2.1.4 описание на русском языке приводится [https://parallel.ru/cluster/fftw.html здесь].&lt;br /&gt;
&lt;br /&gt;
Остальные реализации:&lt;br /&gt;
*[http://www.gnu.org/software/gsl/ GSL - GNU Scientific Library]&lt;br /&gt;
*[https://developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
*[http://www-03.ibm.com/systems/power/software/essl/ Engineering and Scientific Subroutine Library (ESSL) and Parallel ESSL]&lt;br /&gt;
=Литература=&lt;br /&gt;
[1] Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний &lt;br /&gt;
&lt;br /&gt;
[2] Г. Нуссбаумер. Быстрое преобразование Фурье и алгоритмы вычисления сверток &lt;br /&gt;
&lt;br /&gt;
[3] А.О. Лапаев Параллельное вычисление дискретного преобразования Фурье полинома в простом поле&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=18113</id>
		<title>Обсуждение участника:Timokhinivan</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=18113"/>
		<updated>2016-11-18T11:37:21Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4-2.6 не соответствуют содержанию описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
* не указаны авторы статьи и их вклад--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
** Автор один, я (Тимохин Иван, 603 гр. ВМК МГУ) --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 14:50, 27 октября 2016 (MSK)&lt;br /&gt;
*** Так укажите себя, чтобы не было таких вопросов--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 31 октября 2016 (MSK).&lt;br /&gt;
* в п.п. 2.4 следует добавить исходный код программы и описание компьютерной платформы,  с подробным описанием условий, в которых производились эксперименты.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:37, 18 ноября 2016 (MSK)&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* В общем описании алгоритма нужны слова именно про алгоритм: следует рассказать идею быстрого преобразования, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не вполне понятны слова &amp;quot;первообразный корень из 1 степени N&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* В последней формуле раздела 1.2 индексы k и l, кажется, должны идти через запятую. Если там рассматривается их произведение, следует уточнить, какие значения принимает каждый из них [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2.3 - понятие &amp;quot;порядок преобразования&amp;quot; нуждается в пояснении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не хватает слов о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17868</id>
		<title>Участник:Vagabond/Алгоритм быстрого дискретного пребразования Фурье</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17868"/>
		<updated>2016-11-17T10:58:33Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt; {{Assignment|IgorS}}&lt;br /&gt;
&#039;&#039;&#039;Автор&#039;&#039;&#039;: Данилишин А.Р.&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;X=(x_{1},...,x_{n-1})^{T}&amp;lt;/math&amp;gt; - исходный сигнал, который необходимо преобразовать умножением на тензор второго ранга &amp;lt;math&amp;gt;E_{pk}=e^{\frac{-2kpi\pi}{n}} &amp;lt;/math&amp;gt;. Результатом данного преобразования (&amp;lt;math&amp;gt;E*X&amp;lt;/math&amp;gt;) будет вектор &amp;lt;math&amp;gt;Y=(y_{1},...,y_{n-1})^{T}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2kpi\pi}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. Данная формула является классической формулой ДПФ. Известно, что в общем случае умножение матрицы на вектор требует &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; арифметических операций, поэтому возникает вопрос о сокращении данного количества.&lt;br /&gt;
&lt;br /&gt;
Главная идея БПФ состоит в сокращении многочисленных повторов сложения и умножения, которые выполняются в ДПФ (повторяющиеся множители: &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; Например при условии, что  &amp;lt;math&amp;gt;(pk)mod(n)=const&amp;lt;/math&amp;gt;, при всех &amp;lt;math&amp;gt;k=\overline{0,n-1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;p=\overline{0,n-1}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
За счет использования данного факта, происходит значительное сокращение количества вычислительных операций. В данной статье используется алгоритм Cooley – Tukey, который предполагает собой разбиение исходного множества входных сигналов на два равных подмножества. При этом множество входных сигналов должно быть степенью двойки. Далее операция деления множества продолжается для образовавшихся подмножеств, и так далее. В результате над каждым из полученных подмножеств выполняется БПФ. Данные вычисления можно выполнять независимо друг от друга. Этот факт используется для реализации параллельной версии алгоритма. &lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
*Пусть &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\omega _{n}^{kp}&amp;lt;/math&amp;gt;.&lt;br /&gt;
*Разобьем исходный сигнал &amp;lt;math&amp;gt;x(n)&amp;lt;/math&amp;gt; на два подмножества, с четными &amp;lt;math&amp;gt;x_{1}(2n)&amp;lt;/math&amp;gt; и нечетными &amp;lt;math&amp;gt;x_{2}(2n+1)&amp;lt;/math&amp;gt; номерами.&lt;br /&gt;
Тогда получаем: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}\omega _{n}^{kp}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{(2k+1)p}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выпишем два свойства омеги:&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{\frac{n}{2}}=e^{2(\frac{-2i\pi}{n})}=\omega_{n}^{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{n}^{\frac{n}{2}}=e^{\frac{-2ni\pi}{2n}}=e^{-i\pi}=-1&amp;lt;/math&amp;gt;&lt;br /&gt;
Воспользуемся первым: &amp;lt;math&amp;gt;y_{p}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{2kp}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;. Или, если обозначить &amp;lt;math&amp;gt;y_{1}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;y_{2}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, то получим формулу для вычисления координат изображения БПФ при &amp;lt;math&amp;gt;p=\overline{0...n/2}&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;y_(p)=y_{1}(p)+\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заметим, что &amp;lt;math&amp;gt;y(p); y_{1}(p); y_{2}(p)&amp;lt;/math&amp;gt; обладают свойством периодичности, с периодом &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt;. Покажем это для &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt;y_{1}(p+\frac{n}{2})=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{k(p+\frac{n}{2})}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}\omega_{\frac{n}{2}}^{\frac{kn}{2}}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как: &amp;lt;math&amp;gt;\omega_{\frac{n}{2}}^{\frac{kn}{2}}=e^{\frac{-4kni\pi}{2n}}=e^{-2ki\pi}=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Далее необходимо определить &amp;lt;math&amp;gt;y(p)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;p\geq \frac{n}{2}&amp;lt;/math&amp;gt;. Теперь воспользуемся свойством периодичности обнаруженными нами ранее: &amp;lt;math&amp;gt;y(p+\frac{n}{2})=y_{1}(p+\frac{n}{2})+\omega_{n}^{(p+\frac{n}{2})}y_{2}(p+\frac{n}{2})&amp;lt;/math&amp;gt;. Воспользуемся вторым свойством омеги: &amp;lt;math&amp;gt;y(p)=y_{1}(p)-\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;. Все выкладки, приведенные выше, относятся ко второму этапу алгоритма Cooley – Tukey, который был использован в данной статье. Последующие шаги данного этапа делят подмножества полученные на предыдущем шаге вычисления БПФ.&lt;br /&gt;
&lt;br /&gt;
В конечном итоге вышеупомянутый алгоритм сводится к двум этапам:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Бит-реверсирование&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Бит-реверсирование – это преобразование двоичного числа путем изменения порядка следования бит в нем на противоположный. Бит-реверсирование применимо только к индексам элементов массива и предназначено для изменения порядка следования этих элементов, при этом значения самих элементов не изменяются. Так, к примеру, четырехмерный вектор &amp;lt;math&amp;gt;(x_{0},x_{1},x_{2},x_{3})&amp;lt;/math&amp;gt; изменится на &amp;lt;math&amp;gt;(x_{0},x_{2},x_{1},x_{3})&amp;lt;/math&amp;gt;. Данная процедура необходима для разделения исходного сигнала на элементы подмножеств с четными и нечетными номерами, которые необходимы для вычисления &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Вычисление БПФ&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
После выполнения этапа бит-реверсирования выполняются вычисления проиллюстрированные &#039;&#039;на рисунке 1&#039;&#039; в разделе [[#Информационный граф|Информационный граф]]. Рисунок 1 приведен для случая четырехмерного входного сигнала. Он предполагает совершение двух этапов. В общем же количество этапов равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Каждый этап содержит в себе вычисление одной и той же операции &amp;quot;бабочки&amp;quot;, где на вход подаются два числа &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;, на выходе же получаются &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;- это &#039;&#039;коэффициент поворота&#039;&#039;. Общая формула для коэффициента поворота: &amp;lt;math&amp;gt;e^{\frac{-ji\pi}{k}}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;- размер шага &amp;quot;бабочки&amp;quot;, &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;- номер итерации вычислений над группой элементов, размером &amp;lt;math&amp;gt;2k&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;(0\leq j &amp;lt; k )&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из двух вышеописанных этапов БПФ, бит-реверсирование является самым быстровыполнимым. Поэтому вычислительное ядро алгоритма будет лежать именно во втором этапе. А именно, им будет являться вычисление &amp;quot;бабочек&amp;quot;. Количество этапов выполнения БПФ, как описывалось выше, равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Учитывая количество этапов, размерность входного сигнала и идею деления на подмножества, всего &amp;quot;бабочек&amp;quot; получается &amp;lt;math&amp;gt;\frac{nlog_{2}n}{2}&amp;lt;/math&amp;gt;. При этом операция умножения выполняется &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; раз. Это связано с тем, что результаты вычисления &amp;quot;бабочки&amp;quot; &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из всего вышесказанного заключаем, что макроструктуру алгоритма легче всего описать при помощи рекурсии: &lt;br /&gt;
*&amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; преобразований Фурье порядка 2&lt;br /&gt;
*умножение &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; пар комплексных чисел&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; комплексных сложений &lt;br /&gt;
*снова преобразование Фурье&lt;br /&gt;
Также необходимо заметить, что количеством рекурсий будет количество этапов выполнения БПФ, то есть глубина рекурсии будет равняться &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&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;quot;бабочка&amp;quot;&lt;br /&gt;
*выполнение БПФ&lt;br /&gt;
*функция вывода результата (для тестирования)&lt;br /&gt;
----&lt;br /&gt;
&#039;&#039;&#039;главный программный код БПФ:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; *inputSignal = NULL;   &lt;br /&gt;
	complex&amp;lt;double&amp;gt; *outputSignal = NULL; &lt;br /&gt;
	int size = 0;&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;FFT. Serial version.&amp;quot; &amp;lt;&amp;lt; endl;       &lt;br /&gt;
	ProcessInitialization(inputSignal, outputSignal, size);&lt;br /&gt;
	bit(inputSignal, outputSignal, size);&lt;br /&gt;
	SerialFFTCalculation(outputSignal, size);&lt;br /&gt;
	PrintSignal(outputSignal, size); //в случае тестирования&lt;br /&gt;
	ProcessTermination(inputSignal, outputSignal);&lt;br /&gt;
	cin.get();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
=== функция бит-реверсирования ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void bit(complex&amp;lt;double&amp;gt; inputSignal[], complex&amp;lt;double&amp;gt; *outputSignal, int size)&lt;br /&gt;
{ // процесс бит-реверсирования&lt;br /&gt;
	int j = 0, i = 0;&lt;br /&gt;
	while (i &amp;lt; size) &lt;br /&gt;
	{&lt;br /&gt;
		if (j &amp;gt; i) &lt;br /&gt;
		{ &lt;br /&gt;
			outputSignal[i] = inputSignal[j];       &lt;br /&gt;
			outputSignal[j] = inputSignal[i]; &lt;br /&gt;
		}&lt;br /&gt;
		else       &lt;br /&gt;
			if (j == i)          &lt;br /&gt;
				outputSignal[i] = inputSignal[i];&lt;br /&gt;
		&lt;br /&gt;
		int m = size &amp;gt;&amp;gt; 1;     &lt;br /&gt;
		while ((m &amp;gt;= 1) &amp;amp;&amp;amp; (j &amp;gt;= m)) &lt;br /&gt;
		{ &lt;br /&gt;
			j -= m;       &lt;br /&gt;
			m = m &amp;gt;&amp;gt; 1; &lt;br /&gt;
		}     &lt;br /&gt;
		j += m;     &lt;br /&gt;
		i++;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация размерности входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessInitialization(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal, int &amp;amp;size) &lt;br /&gt;
{&lt;br /&gt;
	// инициализация размера входного сигнала  &lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		cout &amp;lt;&amp;lt; &amp;quot;Enter the input signal length: &amp;quot;;&lt;br /&gt;
		cin &amp;gt;&amp;gt; size;&lt;br /&gt;
		if (size &amp;lt; 4)&lt;br /&gt;
			cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be &amp;gt;= 4&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
		else&lt;br /&gt;
		{&lt;br /&gt;
			int tmpSize = size;&lt;br /&gt;
			while (tmpSize != 1)&lt;br /&gt;
			{&lt;br /&gt;
				if (tmpSize % 2 != 0)&lt;br /&gt;
				{&lt;br /&gt;
					cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be powers of two&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
					size = -1;&lt;br /&gt;
					break;&lt;br /&gt;
				}&lt;br /&gt;
				tmpSize /= 2;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	while (size &amp;lt; 4);  &lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Input signal length = &amp;quot; &amp;lt;&amp;lt; size &amp;lt;&amp;lt; endl; &lt;br /&gt;
	inputSignal = new complex&amp;lt;double&amp;gt;[size];   &lt;br /&gt;
	outputSignal = new complex&amp;lt;double&amp;gt;[size];&lt;br /&gt;
	DummyDataInitialization(inputSignal, size);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void DummyDataInitialization(complex&amp;lt;double&amp;gt;* mas, int size)&lt;br /&gt;
{	&lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)&lt;br /&gt;
	{&lt;br /&gt;
		mas[i] = int(rand() % 100);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== процесс очистки памяти ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessTermination(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal) &lt;br /&gt;
{ &lt;br /&gt;
	//очистка памяти&lt;br /&gt;
	delete[] inputSignal;   &lt;br /&gt;
	inputSignal = NULL;   &lt;br /&gt;
	delete[] outputSignal;   &lt;br /&gt;
	outputSignal = NULL; &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение операции &amp;quot;бабочка&amp;quot; ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void Butterfly(complex&amp;lt;double&amp;gt; *signal, complex&amp;lt;double&amp;gt; &amp;amp;u,int offset, int butterflySize)&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; tem = signal[offset + butterflySize] * u;&lt;br /&gt;
	signal[offset + butterflySize] = signal[offset] - tem;   &lt;br /&gt;
	signal[offset] += tem;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение БПФ ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void SerialFFTCalculation(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	int m = 0;   &lt;br /&gt;
	for (int tmp_size = size; tmp_size &amp;gt; 1; tmp_size /= 2, m++);&lt;br /&gt;
	for (int p = 0; p &amp;lt; m; p++)&lt;br /&gt;
	{&lt;br /&gt;
		int butterflyOffset = 1 &amp;lt;&amp;lt; (p + 1);&lt;br /&gt;
		int butterflySize = butterflyOffset &amp;gt;&amp;gt; 1;&lt;br /&gt;
		double coeff = M_PI / butterflySize;&lt;br /&gt;
&lt;br /&gt;
		for (int i = 0; i &amp;lt; size / butterflyOffset; i++)&lt;br /&gt;
			for (int j = 0; j &amp;lt; butterflySize; j++)&lt;br /&gt;
				Butterfly(signal,complex&amp;lt;double&amp;gt;(cos(-j*coeff),sin(-j*coeff)),j+i*butterflyOffset,butterflySize);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== функция вывода результата (для тестирования) ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void PrintSignal(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Result signal&amp;quot; &amp;lt;&amp;lt; endl;   &lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)     &lt;br /&gt;
		cout &amp;lt;&amp;lt; signal[i] &amp;lt;&amp;lt; endl;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотренный алгоритм  Cooley – Tukey выполняется за &amp;lt;math&amp;gt;nlog_{2}n&amp;lt;/math&amp;gt; операций комплексного сложения и &amp;lt;math&amp;gt;\frac{n}{2}log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения. Таким образом последовательная сложность алгоритма является &#039;&#039;&#039;линейно-логарифмической&#039;&#039;&#039;.&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[file:Граф.jpg|center|thumb|800px|Рисунок 1. Алгоритм Cooley – Tukey для n=4, при предварительно сделанном бит-реверсировании]]&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
[[file:Парал.jpg|center|thumb|800px|Рисунок 2. Распараллеливание алгоритма Cooley – Tukey для n=8]]&lt;br /&gt;
Рисунок 2 получен из информационного графа, главной идеей которого является процедура деления множеств полученных на предыдущем шаге на два равных по мощности подмножества и применение к ним БПФ. То есть процесс вычисления распределяется между &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; процессорами. И так как при последовательной реализации алгоритма было &amp;lt;math&amp;gt;\frac{n}{2}\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;n\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. Получается, что при параллельной реализации алгоритма мы будем иметь &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;2\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. При этом, если используется функция HT(Hyper threading), то параллельная сложность быстрого преобразования Фурье (базовый алгоритм Кули-Тьюки) для векторов с длиной, равной степени двойки составляет &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt;.&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
*&#039;&#039;&#039;Вещественное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{R}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
*&#039;&#039;&#039;Комплексное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
*Соотношение последовательной и параллельной сложности является &#039;&#039;линейным&#039;&#039;. Это хорошо видно из пункта [[#Ресурс параллелизма алгоритма|ресурс параллелизма алгоритма]].&lt;br /&gt;
*Вычислительная мощность алгоритма является &#039;&#039;логарифмической&#039;&#039;.&lt;br /&gt;
*Устойчивость. &lt;br /&gt;
**Рассмотрим вопрос об устойчивости поближе. Так как БПФ отличается от ДПФ простым лишь сокращением одинаковых операций, то вопрос устойчивости можно рассматривать на примере ДПФ. Так, к примеру, возьмем формулу ДПФ и добавим возмущение: &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}(x_{k}+\varepsilon )e^{\frac{-2*\pi*k*p*i}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. После чего преобразуем ее: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2*\pi*k*p*i}{n}}+\varepsilon\sum_{k=0}^{n-1} e^{\frac{-2*\pi*k*p*i}{n}}&amp;lt;/math&amp;gt; и оценим вторую сумму, которая является геометрической прогрессией со знаменателем &amp;lt;math&amp;gt;q=e^{\frac{-2*\pi*p*i}{n}}&amp;lt;/math&amp;gt;. Таким образом оцениваемая сумма будет равняться: &amp;lt;math&amp;gt;\varepsilon\frac{1-e^{\frac{-2*\pi*p*i*(n-1))}{n}}}{1-e^{\frac{-2*\pi*p*i}{n}}}&amp;lt;/math&amp;gt;. При достаточно больших размерностях входного сигнала данное выражение сводится к: &amp;lt;math&amp;gt;\varepsilon (1-e^{-2*\pi*p*i})&amp;lt;/math&amp;gt;. В тригонометрической форме данного выражения: &amp;lt;math&amp;gt;\varepsilon (cos(\frac{2*\pi*p*(n-1)}{n})-i*sin(\frac{2*\pi*p*(n-1)}{n}))&amp;lt;/math&amp;gt; видно, что принимая во внимание целочисленность индексов &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; получается 0. Таким образом, мы можем говорить об устойчивости алгоритма в целом, и абсолютной устойчивости алгоритма в асимптотике.&lt;br /&gt;
*исходя из информационного графа, можно сделать вывод о сбалансированности алгоритма.&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:масш.jpg|center|thumb|1000px|Рисунок 3. Параллельная реализация алгоритма. Изменение эффективности(%)в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
Как описывалось выше, каждый процессор получает достаточно низкий объем работы, что приводит к неэффективному распараллеливанию алгоритма (высокие затраты на передачу данных между процессорами по сравнению с вычислениями на них). Поэтому необходимо находить оптимальное количество процессоров для каждой отдельно взятой задачи. В данном примере, при размерности входного сигнала менее 262144, оптимальное количество процессоров составляет 1. При размерности 262144-4194304 оптимум достигался при 8 процессорах. И при размерности более 4194304- 16 процессоров. Вычисления проводились на суперкомпьютере «Ломоносов». &lt;br /&gt;
Код исследуемой программы представлен в разделе [[#Существующие реализации алгоритма|Существующие реализации алгоритма]]&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Самой распространенной и известной реализацией дискретного БПФ является библиотека [http://www.fftw.org/ FFTW], разработанная Маттео Фриго и Стивеном Г. Джонсоном в Массачусетском технологическом институте. Данная библиотека существует для языков C и FORTRAN и позволяет производить вычисления в одном или нескольких измерениях. Входной сигнал может быть как действительным так и комплексным, а его размерность не обязательно должна быть степенью двойки. На данный момент последняя версия FFTW 3.3.5. Другие особенности библиотеки:&lt;br /&gt;
*Скорость. (Поддержка SSE / SSE2 / Altivec, начиная с версии 3.0. Версия 3.3.1 поддерживает AVX и ARM Neon.)&lt;br /&gt;
*Параллельные вычисления (OpenMP, MPI).&lt;br /&gt;
*[http://www.fftw.org/fftw3_doc/ Документация] в форматах HTML, PDF.&lt;br /&gt;
*Данное программное обеспечение является свободным и распространяется по лицензии GNU (General Public License).&lt;br /&gt;
Здесь приводится пример реализации прямого БПФ для комплексных данных входного сигнала на языке C++. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;fftw3-mpi.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/stat.h&amp;gt;&lt;br /&gt;
#include &amp;lt;fcntl.h&amp;gt;&lt;br /&gt;
# include &amp;lt;time.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
	const ptrdiff_t N0 = 4194304;&lt;br /&gt;
	fftw_plan planForw;&lt;br /&gt;
	fftw_complex *data, *dataOut;&lt;br /&gt;
	ptrdiff_t alloc_local, local_ni, local_i_start, i, j, local_no, local_o_start;&lt;br /&gt;
	int index, size;&lt;br /&gt;
	double startwtime, endwtime;&lt;br /&gt;
	MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
	fftw_mpi_init();&lt;br /&gt;
	MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;index);&lt;br /&gt;
	MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);&lt;br /&gt;
&lt;br /&gt;
	/* get local data size and allocate */&lt;br /&gt;
	alloc_local = fftw_mpi_local_size_1d(N0, MPI_COMM_WORLD, FFTW_FORWARD, FFTW_ESTIMATE,&lt;br /&gt;
		&amp;amp;local_ni, &amp;amp;local_i_start, &amp;amp;local_no, &amp;amp;local_o_start);&lt;br /&gt;
	data = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	dataOut = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	/* create plan  */&lt;br /&gt;
	planForw = fftw_mpi_plan_dft_1d(N0, data, dataOut, MPI_COMM_WORLD,&lt;br /&gt;
		FFTW_FORWARD, FFTW_ESTIMATE);&lt;br /&gt;
	/* initialize data to some function my_function(x,y) */&lt;br /&gt;
	for (i = 0; i &amp;lt; local_ni; ++i)&lt;br /&gt;
	{&lt;br /&gt;
		data[i][0] = rand() / (double)RAND_MAX;&lt;br /&gt;
		data[i][1] = rand() / (double)RAND_MAX;&lt;br /&gt;
	}&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		startwtime = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_execute(planForw);&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		endwtime = MPI_Wtime();&lt;br /&gt;
		printf(&amp;quot;wall clock time = %f\n&amp;quot;,&lt;br /&gt;
			endwtime - startwtime);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_destroy_plan(planForw);&lt;br /&gt;
	MPI_Finalize();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
[http://www.fftw.org/download.html Скачав] библиотеку и установив ее на рабочий компьютер, можно поэкспериментировать, используя вышеописанный исходный код. При этом, на стадии компиляции необходимо указывать директорию, где находятся заголовочные файлы и директорию, где находятся библиотечные файлы. Пример: mpicxx name.cpp -I/path/to/include/files -L/path/to/lib/files  -lfftw3_mpi -lfftw3 -lm. Для версии 2.1.4 описание на русском языке приводится [https://parallel.ru/cluster/fftw.html здесь].&lt;br /&gt;
&lt;br /&gt;
Остальные реализации:&lt;br /&gt;
*[http://www.gnu.org/software/gsl/ GSL - GNU Scientific Library]&lt;br /&gt;
*[https://developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
*[http://www-03.ibm.com/systems/power/software/essl/ Engineering and Scientific Subroutine Library (ESSL) and Parallel ESSL]&lt;br /&gt;
=Литература=&lt;br /&gt;
[1] Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний &lt;br /&gt;
&lt;br /&gt;
[2] Г. Нуссбаумер. Быстрое преобразование Фурье и алгоритмы вычисления сверток &lt;br /&gt;
&lt;br /&gt;
[3] А.О. Лапаев Параллельное вычисление дискретного преобразования Фурье полинома в простом поле&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(%D0%91%D0%9F%D0%A4)&amp;diff=17867</id>
		<title>Быстрое дискретное преобразование Фурье (БПФ)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(%D0%91%D0%9F%D0%A4)&amp;diff=17867"/>
		<updated>2016-11-17T10:57:12Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt; {{Assignment|IgorS}}&lt;br /&gt;
Авторы: [[Участник:Chachba.A|Чачба А.Н.]], [[Участник:Weasel|Костоев Р.С.]] &lt;br /&gt;
&lt;br /&gt;
[[Участник:Chachba.A|Чачба А.Н.]] заполнил половину всех заполненных пунктов (а именно 1.1, 1.2, 1.6, 1.8, 1.9) и построил информационный граф алгоритма, [[Участник:Weasel|Костоев Р.С.]] написал программу и провел серию запусков на суперкомпьютере и заполнил вторую половину пунктов. Оба после заполнения исправляли найденные ошибки во всех пунктах.&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразование Фурье (БПФ)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Преобразование Фурье - взаимно однозначное отображение одной функции &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; вещественной переменной, называемой таргетным сигналом, с другой функцией &amp;lt;math&amp;gt;\hat{f}&amp;lt;/math&amp;gt; вещественной переменной, называемой образом Фурье или спектром исходной функции по формуле:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\hat{f}(\omega)=\frac{1}{\sqrt{2\pi}}\int\limits_{-\infty}^{\infty}f(x)e^{-ix\omega}\,dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Дискретное преобразование Фурье, в свою очередь, есть аналог непрерывного преобразования Фурье, но для дискретного сигнала содержащего &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; отсчетов.&lt;br /&gt;
Широко применяется в цифровой обработке сигналов, теории вероятностей, криптографии и акустике.&lt;br /&gt;
Преобразование Фурье обратимо, причем обратное преобразование имеет практически ту же форму, что и прямое.&lt;br /&gt;
Преобразование Фурье имеет сложность &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, но существует быстрый вариант преобразование Фурье со сложностью &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
Такая скорость достигается благодаря применению схемы &amp;quot;разделяй и властвуй&amp;quot;. Общий вид преобразования позволяет разделить массив на несколько частей таким образом, что, применяя преобразование Фурье к отдельным его частям и объединяя специальным образом результаты (при помощи поворотных множителей и повторного применения преобразования), получать на выходе образ исходного вектора. Но благодаря разделению массива исходного размера на части суммарная асимптотическая сложность алгоритма уменьшается.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть исходный сигнал имеет значения &amp;lt;math&amp;gt;x_n,\quad n = 0,\dots,N-1&amp;lt;/math&amp;gt;, тогда дискретное прямое преобразование Фурье (ДПФ) имеет вид:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
X_k = \sum_{n=0}^{N-1}x_ne^{-\frac{2\pi i}{N}kn},\quad k = 0, \dots, N-1&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Обозначим &amp;lt;math&amp;gt; \varepsilon_{N} = e^{-\frac{2\pi i}{N}}&amp;lt;/math&amp;gt;, тогда ДПФ можно перезаписать в матричной форме:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\bar X = A\bar x&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где матрица &amp;lt;math&amp;gt;A = \{\varepsilon_{N}^{(i - 1)(j - 1)}\}_{i,j=1}^{N}&amp;lt;/math&amp;gt;&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Пусть, для простоты &amp;lt;math&amp;gt; N = km&amp;lt;/math&amp;gt;, тогда рекурсивная реализация преобразования Фурье, за счет &amp;lt;math&amp;gt;{k}&amp;lt;/math&amp;gt; рекурсий на первом этапе и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; на последнем этапе, имеет суммарную сложность &amp;lt;math&amp;gt;O(Nk + Nm + km) = O(N(k + m))&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
В случае, например &amp;lt;math&amp;gt;N = 2^n&amp;lt;/math&amp;gt; сложность БПФ составляет &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В общем же случае, когда &amp;lt;math&amp;gt;N = \prod_{i=1}^np_i&amp;lt;/math&amp;gt; сложность БПФ составляет &amp;lt;math&amp;gt; O(N(\sum_{i=1}^np_i))&amp;lt;/math&amp;gt;. Так как рекурсия применяется каждый раз при отщеплении одного множителя, то можно заключить, что глубина рекурсии алгоритма составляет &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-число множителей в факторизации &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В свою очередь, если число &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; не факторизуется в произведение простых множителей (то есть &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; - простое число), то данный алгоритм не применим. На замену ему приходит алгоритм Радера, имеющий худшую асимптотику, нежели обычный БПФ, но лучшую чем &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;. Также алгоритм Радера потребует дополнительного взаимодействия процессоров, что приведет к ухудшению эффективности.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Макроструктура БПФ для случая &amp;lt;math&amp;gt;N = km&amp;lt;/math&amp;gt; описывается рекурсивно:&lt;br /&gt;
# &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; независимых преобразований векторов меньшей размерности &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;&lt;br /&gt;
#Умножение элементов на поворотные коэффициенты (&amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; умножений)&lt;br /&gt;
# &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; обратных преобразований векторов размерностей &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Рекурсивный метод для случая &amp;lt;math&amp;gt;N = 2^k&amp;lt;/math&amp;gt;, без оптимизации на C++:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;#include &amp;lt;vector&amp;gt;&lt;br /&gt;
#include &amp;lt;complex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using namespace std;&lt;br /&gt;
&lt;br /&gt;
typedef complex&amp;lt;double&amp;gt; cd;&lt;br /&gt;
typedef vector&amp;lt;cd&amp;gt; vcd;&lt;br /&gt;
&lt;br /&gt;
vcd fft(const vcd &amp;amp;as) { &lt;br /&gt;
  int n = as.size();&lt;br /&gt;
  if (n == 1) return vcd(1, as[0]);&lt;br /&gt;
&lt;br /&gt;
  vcd w(n); // Calculate roots&lt;br /&gt;
  for (int i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
    double alpha = 2 * M_PI * i / n;&lt;br /&gt;
    w[i] = cd(cos(alpha), sin(alpha));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  vcd A(n / 2), B(n / 2);&lt;br /&gt;
  for (int i = 0; i &amp;lt; n / 2; i++) {&lt;br /&gt;
    A[i] = as[i * 2];&lt;br /&gt;
    B[i] = as[i * 2 + 1];&lt;br /&gt;
  }&lt;br /&gt;
  vcd Av = fft(A);&lt;br /&gt;
  vcd Bv = fft(B);&lt;br /&gt;
  vcd res(n);&lt;br /&gt;
  for (int i = 0; i &amp;lt; n; i++)&lt;br /&gt;
    res[i] =   Av[i % (n / 2)] +&lt;br /&gt;
        w[i] * Bv[i % (n / 2)];&lt;br /&gt;
  return res;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
В последовательном варианте можно бороться за улучшение константы сложности, предподсчитав значения соответствующих коэффициентов.&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Алгоритм состоит из трех этапов, следовательно если &amp;lt;math&amp;gt;N = \prod_{i = 1}^np_i&amp;lt;/math&amp;gt;, то общая сложность составляет порядка &amp;lt;math&amp;gt;O(N\sum_{i=1}^np_i)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Пусть существует некоторое фиксированное число &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;, такое что:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
p_i \leq P \quad \forall i = 1...n,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
тогда, учитывая, что &amp;lt;math&amp;gt;n \leq \log_{P}{N}&amp;lt;/math&amp;gt;, сложность алгоритма можно записать в виде &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[Файл:FourierGraph2.png|center|thumb|640px|&#039;&#039;&#039;Рисунок 1.&#039;&#039;&#039; Информационный граф преобразования Кули-Тьюки для &amp;lt;math&amp;gt;N = km = 25, k = 5, m = 5&amp;lt;/math&amp;gt;. Желтые вершины - умножение на поворотные множители.]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
В силу независимости всех рекурсивных вызовов и того факта, что путь максимальной длины в графе (так называемый критический путь) имеет порядок &amp;lt;math&amp;gt;O(\log{N})&amp;lt;/math&amp;gt;, можно утверждать, что параллельная сложность алгоритма составит &amp;lt;math&amp;gt;O(\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Замечание&#039;&#039;: формально основание логарифма на каждом рекурсивном шаге зависит от множителя в факторизации &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, потому использована О-символика.&lt;br /&gt;
&lt;br /&gt;
При условии ограниченности количества доступных вычислительных узлов рекомендуется выбирать множители в разложении числа $N$ близкими к числу доступных узлов, таким образом доступные ресурсы будут использованы максимально эффективно.&lt;br /&gt;
&lt;br /&gt;
Итого алгоритм относится к логарифмическому классу алгоритмов по параллельной сложности.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
Чаще всего для обработки сигналов в качестве входных данных для БПФ подается вектор размерности &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; вещественных элементов. Но БПФ работает и для случая элементов над комплексным полем. Таким образом, например, можно экономить на количестве применений БПФ в некоторой конкретной задаче путем приведения двух векторов вещественных чисел к одному вектору комплексных чисел с вещественной частью равной первому вектору и комплексной частью равной второму вектору:&lt;br /&gt;
&amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; - исходные сигналы. Обозначим &amp;lt;math&amp;gt;z = x + iy&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;F(k, x)&amp;lt;/math&amp;gt; - это &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;-ый коэффициент образа Фурье вектора &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;. Тогда:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, x) = \frac{F(k, z) + F(k, \bar z)}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, y) = -i\frac{F(k, z) - F(k, \bar z)}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Учитывая, что &amp;lt;math&amp;gt;F(k, \bar z) = \overline{F(N - k, z)}&amp;lt;/math&amp;gt;, получаем:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, x) = \frac{F(k, z) + \overline{F(N-k, z)}}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, y) = -i\frac{F(k, z) - \overline{F(N-k,z)}}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Такой способ позволяет приблизительно в два раза уменьшить количество вычислений.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
Вектор размерности &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; комплексных чисел - спектр исходного сигнала.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Матрица Вандермонда преобразования &amp;lt;math&amp;gt;A = \{e^{-\frac{2\pi i}{N}(i - 1)(j - 1)}\}_{i,j=1}^{N}&amp;lt;/math&amp;gt; такая, что:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
A^{-1} = \frac{1}{N}A^*&lt;br /&gt;
&amp;lt;/math&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;
Все запуски программы[http://pastebin.com/raw/xajKb9JN] проводились на с/к Blue Gene/P с использованием библиотеки Intel MKL.&lt;br /&gt;
&lt;br /&gt;
IBM Blue Gene/P — массивно-параллельная вычислительная система, которая состоит из двух стоек, включающих 2048 четырехъядерных вычислительных узлов, с пиковой производительностью 27,9 терафлопс.&lt;br /&gt;
&lt;br /&gt;
Характеристики системы:&lt;br /&gt;
* две стойки с вычислительными узлами и узлами ввода-вывода&lt;br /&gt;
* 1024 четырехъядерных вычислительных узла в каждой из стоек&lt;br /&gt;
* 16 узлов ввода-вывода в стойке (в текущей конфигурации активны 8, т.е. одна I/O-карта на 128 вычислительных узлов)&lt;br /&gt;
* программирование с использованием MPI, OpenMP/pthreads, POSIX I/O&lt;br /&gt;
* высокая энергоэффективность: ∼ 372 MFlops/W&lt;br /&gt;
&lt;br /&gt;
Вычислительный узел включает в себя четырехъядерный процессор, 2 ГБ общей памяти и сетевые интерфейсы.&lt;br /&gt;
&lt;br /&gt;
Вычислительной узел:&lt;br /&gt;
* четыре микропроцессорных ядра PowerPC 450 (4-way SMP)&lt;br /&gt;
* пиковая производительность: 4 ядра x 3,4 GFlop/sec на ядро = 13,6 GFlop/sec&lt;br /&gt;
* пропускная способность памяти: 13,6 GB/sec&lt;br /&gt;
* 2 ГБ общей памяти&lt;br /&gt;
* 2 x 4 МБ кэш-памяти 2-го уровня (в документации по BG/P носит название L3)&lt;br /&gt;
* легковесное ядро (compute node kernel, CNK), представляющее собой Linux-подобную операционную систему, поддерживающую значительное подмножество Linux-совместимых системных вызовов&lt;br /&gt;
* асинхронные операции межпроцессорных обменов (выполняются параллельно с вычислениями)&lt;br /&gt;
* операции ввода-вывода перенаправляются I/O-картам через сеть коллективных операций&lt;br /&gt;
&lt;br /&gt;
[[Файл:Time.PNG|center|thumb|640px|&#039;&#039;&#039;Рисунок 2.&#039;&#039;&#039; Время выполнения параллельной реализации БПФ в секундах. Size - число элементов массива. Р - число использованных процессоров.]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Efff.PNG|center|thumb|640px|&#039;&#039;&#039;Рисунок 3.&#039;&#039;&#039; Эффективность параллельной реализации БПФ. Size - число элементов массива. Р - число использованных процессоров. Эффективность в пределах от 0 до 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;
* Самая популярная библиотека для работы с преобразованиями Фурье это [http://www.fftw.org FFTW]&lt;br /&gt;
* Реализация от Intel в рамках [//software.intel.com/en-us/intel-mkl Math Kernel Library]&lt;br /&gt;
* Реализация ДПФ на графических картах от NVidia - [//developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
* Бахвалов Н. С.,  Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.&lt;br /&gt;
* Описание алгоритма на Википедии: [https://ru.wikipedia.org/wiki/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5 Быстрое преобразование Фурье]&lt;br /&gt;
* Материалы лекций по &amp;quot;Алгоритмам и структурам данных&amp;quot; Школы Анализа Данных Яндекса&lt;/div&gt;</summary>
		<author><name>IgorS</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:Ivanov.kir.m/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17862</id>
		<title>Участник:Ivanov.kir.m/Быстрое дискретное преобразование Фурье</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:Ivanov.kir.m/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17862"/>
		<updated>2016-11-17T09:33:46Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt; {{Assignment|IgorS}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Алгоритм одномерного преобразования Фурье для действительных чисел&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O (N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O (\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; действительных чисел&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;\lfloor N/2 \rfloor+1&amp;lt;/math&amp;gt; комплексных чисел&lt;br /&gt;
}}&lt;br /&gt;
Авторы описания [[Участник:Ivanov.kir.m | К.М.Иванов]], [[Участник:Pumpkin | А.C.Сапин]] Вклад каждого участника равномерный, поскольку каждый пункт вычитывался и исправлялся обоими участниками. При сильном приближении можно сказать,что больший вклад [[Участник:Ivanov.kir.m | К.М.Иванов]] внес в заполнение теоретической части, а [[Участник:Pumpkin | А.C.Сапин]] в заполнение практической части. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Быстрое преобразование Фурье (БПФ, FFT)&#039;&#039;&#039; — алгоритм быстрого  вычисления дискретного преобразования Фурье (ДПФ). То есть, алгоритм вычисления за количество действий, меньшее чем &amp;lt;math&amp;gt;O(N^{2})&amp;lt;/math&amp;gt;, требуемых для прямого (по формуле) вычисления ДПФ. Иногда под БПФ понимается один из быстрых алгоритмов, называемый алгоритмом прореживания по частоте/времени или алгоритмом по основанию 2, имеющий сложность &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt;. Cуществует несколько различных алгоритмов для вычисления ДПФ считающимся быстрым преобразование Фурье:&lt;br /&gt;
* Алгоритм Кули-Тьюки [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm]&lt;br /&gt;
* Алгоритм Гуда-Томаса [https://en.wikipedia.org/wiki/Prime-factor_FFT_algorithm]&lt;br /&gt;
* Алгоритм Бруна  [https://en.wikipedia.org/wiki/Bruun%27s_FFT_algorithm]&lt;br /&gt;
* Алгоритм Блюштейна [https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm]&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Быстрое дискретное преобразование Фурье&#039;&#039;&#039; (&#039;&#039;БПФ&#039;&#039;) - популярный в современном мире математический метод преобразования вектора/матрицы комплексных/действительных чисел - &#039;&#039;сигнала&#039;&#039; - в вектор/матрицу комплексных чисел - &#039;&#039;спектр&#039;&#039;. С математической точки зрения преобразование сигнала &amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; (как функции по времени &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt;) в спектр &amp;lt;math&amp;gt;\hat{s}&amp;lt;/math&amp;gt; описывается формулой&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\hat{s}(\omega)=\frac{1}{\sqrt{2\pi}}\int\limits_{-\infty}^{\infty}s(t)e^{-it\omega}\,dt&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В отличии от своего классического аналога БПФ имеет сложность &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;, а не &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, это достигается благодаря разделению исходного вектора на несколько частей, так чтобы применив к этим частям БПФ и объединив результаты путем умножения на поворотные множители и дополнительного БПФ, можно было получить преобразование Фурье исходного вектора - принцип &amp;quot;разделяй и властвуй&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Область применения данного метода простирается от обработки аудио информации до собственно спектрального анализа.&lt;br /&gt;
Поскольку в общем случае алгоритм БПФ применим к произвольным векторам чисел, выделяют отдельные подзадачи, для которых можно ввести дополнительную оптимизацию. Одной из таких подзадач является быстрое преобразование Фурье вектора действительных чисел, которому и посвящена данная статья. В качестве тестируемой реализации был выбран алгоритм Кули-Тьюки в библиотеке FFTW, написанной на языке C.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание&#039;&#039;&#039;: Поскольку общие описание алгоритма БПФ весьма сложно, в некоторых пунктах данная статья будет ссылаться на простой случай - алгоритм Кули-Тьюки быстрого преобразование Фурье для векторов &#039;&#039;с размерностью равной степени двойки&#039;&#039;. Отличительной особенностью данного алгоритма является то, что он обходится без использования специфических приемов, использующихся именно для степеней четверки, восьмерки и т.п. Однако благодаря тому, что на вход данному алгоритму подается вектор чисто вещественных чисел, выходной вектор удовлетворяет &#039;&#039;эрмитовой избыточности&#039;&#039; (&#039;&#039;Hermitian redundancy&#039;&#039;) , т.е.  &amp;lt;math&amp;gt;out[i]&amp;lt;/math&amp;gt; является сопряженным с &amp;lt;math&amp;gt;out[n-i]&amp;lt;/math&amp;gt;. Это обстоятельство позволяет достичь роста скорости и снижения затрат памяти примерно в 2 раза по сравнению с &#039;&#039;комплексным&#039;&#039; аналогом алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Входные данные: вектор действительных чисел &amp;lt;math&amp;gt;x = (x_1,x_2,...,x_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выходные данные: вектор комплексных чисел &amp;lt;math&amp;gt;X = (X_1,X_2,...,X_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Дискретное преобразование Фурье задается следующей формулой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;X_k = \sum_{n=0}^{N-1} x_n e^{-\frac{2\pi i}{N} nk}, k = \overline{0,N-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В простейшем случае(для степеней двойки) алгоритм Кули-Тьюки рассчитывает ДПФ для четных &amp;lt;math&amp;gt;(x_{2m}=x_0, x_2, \ldots, x_{N-2})&amp;lt;/math&amp;gt; и нечетных элементов отдельно &amp;lt;math&amp;gt;(x_{2m+1}=x_1, x_3, \ldots, x_{N-1})&amp;lt;/math&amp;gt;. Затем результаты рассчетов объединяются для получения ДПФ всей последовательности: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{matrix} X_k &amp;amp; =&lt;br /&gt;
&amp;amp; \sum \limits_{m=0}^{N/2-1} x_{2m}e^{-\frac{2\pi i}{N} (2m)k}   +   \sum \limits_{m=0}^{N/2-1} x_{2m+1} e^{-\frac{2\pi i}{N} (2m+1)k}&lt;br /&gt;
  \end{matrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, получается рекурсивный алгоритм, работающий по принципу [https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0) разделяй и влавствуй].&lt;br /&gt;
&lt;br /&gt;
=== Рекурсивное описание ===&lt;br /&gt;
Алгоритм:&lt;br /&gt;
# Входной вектор &amp;lt;math&amp;gt;a = (a_0,a_2,...,a_{N-1})&amp;lt;/math&amp;gt; преобразуется в матрицу &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; размера &amp;lt;math&amp;gt;n_1 \times n_2 &amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;N=n_1 \cdot n_2&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_1 &amp;lt; n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; A = \begin{pmatrix}&lt;br /&gt;
  a_0 &amp;amp; a_1 &amp;amp; \cdots &amp;amp; a_{n_1-1} \\&lt;br /&gt;
  a_{n_1} &amp;amp; a_{n_1+1} &amp;amp; \cdots &amp;amp; a_{2n_1-1} \\&lt;br /&gt;
  \vdots  &amp;amp; \vdots  &amp;amp; \ddots &amp;amp; \vdots  \\&lt;br /&gt;
  a_{(n_2-1)\cdot n_1} &amp;amp; a_{(n_2-1)\cdot n_1+1} &amp;amp; \cdots &amp;amp; a_{n_2\cdot n_1-1} &lt;br /&gt;
 \end{pmatrix} &amp;lt;/math&amp;gt;&lt;br /&gt;
# К каждой строке полученной матрицы применяется быстрое дискретное преобразование Фурье (БПФ) порядка &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Каждый элемент полученный после применения БПФ умножается на &#039;&#039;поворотные множители&#039;&#039; &amp;lt;math&amp;gt;exp (2 \pi i(m-1)(j-1)/N)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; - номер строки, а &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; - номер столбца&lt;br /&gt;
# Полученная после шагов 1-2 матрица &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; транспонируется&lt;br /&gt;
# К каждой строке матрицы &amp;lt;math&amp;gt;A^T&amp;lt;/math&amp;gt; применяется БПФ порядка &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как алгоритм реализует принцип &amp;quot;разделяй и властвуй&amp;quot; глубина рекурсии составляет &amp;lt;math&amp;gt;O (\log N)&amp;lt;/math&amp;gt; от числа входных элементов.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; Как правило все &#039;&#039;поворотные множители&#039;&#039; вычисляются заранее и хранятся в специальном массиве.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; В общем случае &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; разбивается на &amp;lt;math&amp;gt;n_1 \cdot n_2&amp;lt;/math&amp;gt; так, чтобы &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; сами не являлись простыми числами и разлагались на множители не являющиеся простыми числами.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; В случае простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; оно дополняется(как правило единицией) до непростого числа. В процессе слияния добавленные значения отбрасываются.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В случае размерности входа равной степени двойки, вычислительным ядром алгоритма является так называемая, &amp;quot;&#039;&#039;бабочка&#039;&#039;&amp;quot;. В простейшем случае &amp;quot;&#039;&#039;бабочка&#039;&#039;&amp;quot; представляет из себя двухточечное преобразование. Рассмотрим этот случай:&lt;br /&gt;
&lt;br /&gt;
На вход алгоритму подается двухэлементный вектор ‒ &amp;lt;math&amp;gt; v = (v[0], v[1]) &amp;lt;/math&amp;gt;. Тогда для вычисления будут происходить по следующим формулам:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[0] = W_2^0 v[0] + W_2^0 v[1] = v[0] + W_2^0 v[1] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[1] = W_2^0 v[0] + W_2^1 v[1] = v[0] + W_2^1 v[1] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Данный процесс удобно изобразить с помощью следующей схемы:&lt;br /&gt;
&lt;br /&gt;
[[file:Fftbutterfly.png | Рис. 1. &amp;quot;&#039;&#039;Бабочка&#039;&#039;&amp;quot; для двумерного входа ]]&lt;br /&gt;
&lt;br /&gt;
Для 4-х элеметного вектора &amp;lt;math&amp;gt;v=(v[0],v[1],v[2],v[3])&amp;lt;/math&amp;gt;, алгоритм строится похожим образом. Сначала создаются простейшие &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot;, а потом их результаты соединяются с противоположеной &amp;quot;бабочкой&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[0]=v[0]+W_2^0 v[2]+W_4^0(v[1]+W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[1]=v[0]-W_2^0 v[2]+W_4^1(v[1]-W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[2]=v[0]+W_2^0 v[2]-W_4^0(v[1]+W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[3]=v[0]-W_2^0 v[2]-W_4^1(v[1]-W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Схема в таком случае будет выглядеть следующим образом:&lt;br /&gt;
&lt;br /&gt;
[[file:Fftbutterfly4.png| Рис. 2. &amp;quot;&#039;&#039;Бабочка&#039;&#039;&amp;quot; для четырёхмерного входа ]]&lt;br /&gt;
&lt;br /&gt;
Для случая, когда вход не является степенью двойки, &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot; будут &amp;quot;&#039;&#039;несимментричными&#039;&#039;&amp;quot;, но в остальном вычисления будут проходить схожим образом.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Для исходного вектора &amp;lt;math&amp;gt;a = (a_1,a_2,...,a_N)&amp;lt;/math&amp;gt; размерности &amp;lt;math&amp;gt;N = n_1 \cdot n_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n_1 &amp;lt; n_2&amp;lt;/math&amp;gt; БПФ представляется как:&lt;br /&gt;
#&amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; БПФ порядка &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; &lt;br /&gt;
#&amp;lt;math&amp;gt;n_1 \cdot n_2&amp;lt;/math&amp;gt; умножение комплексных чисел &lt;br /&gt;
#&amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; БПФ порядка &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Схема организации состоит в том, что на каждом шаге &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; для выполнения &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot; все элементы разбиваются на &amp;lt;math&amp;gt;n_{i_1}&amp;lt;/math&amp;gt; векторов по &amp;lt;math&amp;gt;n_{i_2}&amp;lt;/math&amp;gt; элементов, причем &amp;lt;math&amp;gt;n_{i_1} \cdot n_{i_2} = n_i&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;n_i&amp;lt;/math&amp;gt; длина входного вектора на текущем шаге &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;. В случае если такое разбиение невозможно, по причине того что &amp;lt;math&amp;gt;n_i&amp;lt;/math&amp;gt; простое число, исходный вектор на текущем шаге дополняется элементом. В зависимости от номера шага, разница координат для каждой пары элементов увеличивается соразмерно разбиению  &amp;lt;math&amp;gt;(n_{i_1},n_{i_2})&amp;lt;/math&amp;gt;.&lt;br /&gt;
При этом результат суммы записывается в элемент с меньшим номером, а результат вычитания с последующим умножением - в элемент с большим.&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Быстрое дискретное преобразование Фурье выполнимо за &amp;lt;math&amp;gt;O(N(n_1+\cdots+n_m))&amp;lt;/math&amp;gt; действий при &amp;lt;math&amp;gt;N=n_1n_2\cdots n_m&amp;lt;/math&amp;gt; (в простом случае, при &amp;lt;math&amp;gt;N=2^m&amp;lt;/math&amp;gt; необходимо &amp;lt;math&amp;gt;O(N\log_2(N))&amp;lt;/math&amp;gt; действий). Дискретное преобразование Фурье преобразует вектор &amp;lt;math&amp;gt;a = (a_0, \dots, a_{N-1})&amp;lt;/math&amp;gt; в вектор комплексных чисел &amp;lt;math&amp;gt;b = (b_0, \dots, b_{N-1})&amp;lt;/math&amp;gt;, такой, что &amp;lt;math&amp;gt;b_i=\sum_{j=0}^{N-1}a_j\varepsilon^{ij}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\varepsilon^n=1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\varepsilon^k\neq 1&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;0&amp;lt;k&amp;lt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной шаг алгоритма состоит в сведении задачи для &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; чисел к задаче для &amp;lt;math&amp;gt;n_1=N/n_2&amp;lt;/math&amp;gt; числам, где &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; — делитель &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть мы уже умеем решать задачу для &amp;lt;math&amp;gt;N/n_2&amp;lt;/math&amp;gt; чисел. Применим преобразование Фурье к векторам &amp;lt;math&amp;gt;a_i,a_{n_2+i}, \dots, a_{n_2(n_1-1)+i}&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;i=0,1,\dots,n_2-1&amp;lt;/math&amp;gt;. Покажем теперь, что за &amp;lt;math&amp;gt;O(Np)&amp;lt;/math&amp;gt; действий можно решить исходную задачу. Заметим, что &amp;lt;math&amp;gt;b_i=\sum_{j=0}^{n_2-1} \varepsilon^{ij} \left(\sum_{k=0}^{n_1-1}a_{kn_2+j}\varepsilon^{kin_2}\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Выражения в скобках нам уже известны — это &amp;lt;math&amp;gt;(i\mod p)&amp;lt;/math&amp;gt;-тое число после преобразования Фурье &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;-го вектора. Таким образом, для вычисления каждого &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; нужно &amp;lt;math&amp;gt;O(n_2)&amp;lt;/math&amp;gt; действий, а для вычисления всех &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; всего &amp;lt;math&amp;gt;O(Nn_2)&amp;lt;/math&amp;gt; действий, что и требовалось.&lt;br /&gt;
&lt;br /&gt;
В общем случае:&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;4N&amp;gt;2^k\ge2N&amp;lt;/math&amp;gt;. Заметим, что тогда &amp;lt;math&amp;gt;b_i=\varepsilon^{-i^2/2}\sum_{j=0}^{N-1}\varepsilon^{(i+j)^2/2}\varepsilon^{-j^2/2}a_j&amp;lt;/math&amp;gt;. Обозначим &amp;lt;math&amp;gt;\bar{a}_i=\varepsilon^{-i^2/2}a_i&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;\bar{b}_i=\varepsilon^{i^2/2}b_i&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;c_i=\varepsilon^{(2N-2-i)^2/2}&amp;lt;/math&amp;gt;. Тогда &amp;lt;math&amp;gt;\bar{b}_i=\sum_{j=0}^{2N-2-i}\bar{a}_jc_{2N-2-i-j}&amp;lt;/math&amp;gt;, если положить &amp;lt;math&amp;gt;\bar{a}_i=0&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;i\ge N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом задача сведена к вычислению &#039;&#039;свёртки&#039;&#039;, а это можно сделать с помощью трёх преобразований Фурье для &amp;lt;math&amp;gt;2^k&amp;lt;/math&amp;gt; элементов. Выполняем прямое преобразование Фурье для &amp;lt;math&amp;gt;(\bar{a_0}, \cdots, \bar{a}_{2^k-1})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(c_1,\cdots,c_{2^k-1})&amp;lt;/math&amp;gt;, перемножаем поэлементно результаты и выполняем обратное преобразование Фурье.&lt;br /&gt;
&lt;br /&gt;
Вычисления всех &amp;lt;math&amp;gt;\bar{a}_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_i&amp;lt;/math&amp;gt; требуют &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий, три преобразования Фурье требуют &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt; действий, перемножение результатов преобразований Фурье требует &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий, вычисление всех &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; зная значения свертки требует &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий. Итого для дискретного преобразования Фурье требуется &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt; действий для любого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[Файл:InfoGraph Ivanov Sapin.png |center|thumb|800px| Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 21&amp;lt;/math&amp;gt;. Исходные данные обозначены зеленым, результат — синим.  Преобразования Фурье для &amp;lt;math&amp;gt;n_1 = 7&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2 = 3 \text{   } (N = n_1 \cdot n_2)&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты (множители) представлено коричневыми ромбами.]]&lt;br /&gt;
Как видно из рисунка, размер этого графа линейно-логарифмический, а формулы дуг имеют экспоненциальные компоненты.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Если считать только главные члены выражений, то простой алгоритм Кули-Тьюки имеет критический путь, состоящий из &amp;lt;math&amp;gt;log(N)&amp;lt;/math&amp;gt; операций комплексного сложения/вычитания и &amp;lt;math&amp;gt;log(N)&amp;lt;/math&amp;gt; операций комплексного умножения (основание &amp;lt;math&amp;gt;log&amp;lt;/math&amp;gt; целиком зависит от выбранного на каждом шаге алгоритма разбиения). Здесь стоит отметить, что все рекурсивные вызовы БПФ на каждом шаге выполняются независимо, это приводит к тому, что их можно распределить по доступным вычислительным узлам. Аналогичная ситуация обстоит и с умножением на поворотные множетели, что позволяет независимо присоединять его к любому шагу. Таким образом алгоритм БПФ имеет высокий ресурс параллелизма, а алгоритм БПФ в общем случае может быть отнесён к &#039;&#039;логарифмическому&#039;&#039; классу по параллельной сложности. &lt;br /&gt;
&lt;br /&gt;
Кроме того, параллелизм БПФ можно увеличить, если брать &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; по возможности близкими к кратным доступному количеству вычислительных узлов (а не брать просто один из множителей близким к нулю, как в традиционной реализации), это позволит равномерно распределить работу между вычислительными узлами, что сильно повысит эффективность алгоритма благодаря лишь одной внутренней пересылке данных.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; вектор действительных/комплексных чисел &amp;lt;math&amp;gt;a = (a_1,a_2,...,a_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; вектор комплексных чисел &amp;lt;math&amp;gt;b = (b_1,b_2,...,b_{\lfloor N/2 \rfloor+1})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Есть несколько ситуаций, при которых количество потребляемой памяти для хранения входного вектора &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и выходного вектора &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; тможет быть снижено вдвое: &lt;br /&gt;
# Если &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; — вектор действительных чисел, тогда &amp;lt;math&amp;gt;b = E\bar{b}&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; — вектор чисто-мнимых чисел, тогда &amp;lt;math&amp;gt;b = -E\bar{b}&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a = E\bar{a}&amp;lt;/math&amp;gt;, тогда &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; — вектор действительных чисел;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a = -E\bar{b}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; — вектор чисто-мнимых чисел.&lt;br /&gt;
&lt;br /&gt;
Где матрица &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  E = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0 \\ &lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 1 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 1 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \ddots &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 1 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 1 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Таким образом в общем у алгоритма БПФ в случае неограниченных ресурсов:&lt;br /&gt;
*последовательная сложность - &#039;&#039;линейно-логарифмическая&#039;&#039;&lt;br /&gt;
*параллельная сложность  - &#039;&#039;логарифмическая&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
При этом алгоритм полностью детерминирован.&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
В качестве тестируемой реализации была выбрана библиотека FFTW, а именно функция, выполняющая БПФ одномерного вектора действительных чисел.&lt;br /&gt;
==  Особенности последовательной реализации алгоритма ==&lt;br /&gt;
Простейший вариант алгоритма для степеней двойки на языке C:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
#include &amp;lt;complex.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
typedef double complex cplx;&lt;br /&gt;
 &lt;br /&gt;
void _fft(cplx *buf, cplx *out, int n, int step)&lt;br /&gt;
{&lt;br /&gt;
    if (step &amp;lt; n) {&lt;br /&gt;
	_fft(out, buf, n, step * 2); //рекурсивный рассчет&lt;br /&gt;
	_fft(out + step, buf + step, n, step * 2);&lt;br /&gt;
 &lt;br /&gt;
	for (int i = 0; i &amp;lt; n; i += 2 * step) {&lt;br /&gt;
	    cplx t = cexp(-I * PI * i / n) * out[i + step];&lt;br /&gt;
	    buf[i / 2]     = out[i] + t;&lt;br /&gt;
	    buf[(i + n)/2] = out[i] - t;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
void fft(cplx *buf, int n)&lt;br /&gt;
{&lt;br /&gt;
    cplx out[n];&lt;br /&gt;
    for (int i = 0; i &amp;lt; n; i++) out[i] = buf[i];&lt;br /&gt;
 &lt;br /&gt;
    _fft(buf, out, n, 1);&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;
Тестирование проводилось на вычислительном комплексе IBM Blue Gene/P&lt;br /&gt;
&lt;br /&gt;
=== Описание платформы IBM Blue Gene/P ===&lt;br /&gt;
IBM Blue Gene/P — массивно-параллельная вычислительная система, которая состоит из двух стоек, включающих 8192 процессорных ядер (2 x 1024 четырехъядерных вычислительных узлов), с пиковой производительностью 27,9 терафлопс (27,8528 триллионов операций с плавающей точкой в секунду).&lt;br /&gt;
&lt;br /&gt;
Характеристики системы:&lt;br /&gt;
&lt;br /&gt;
* две стойки с вычислительными узлами и узлами ввода-вывода&lt;br /&gt;
* 1024 четырехъядерных вычислительных узла в каждой из стоек&lt;br /&gt;
* 16 узлов ввода-вывода в стойке (в текущей конфигурации активны 8, т.е. одна I/O-карта на 128 вычислительных узлов)&lt;br /&gt;
* выделенные коммуникационные сети для межпроцессорных обменов и глобальных операций&lt;br /&gt;
* программирование с использованием MPI, OpenMP/pthreads, POSIX I/O&lt;br /&gt;
* высокая энергоэффективность: ∼ 372 MFlops/W (см. список Green500)&lt;br /&gt;
* система воздушного охлаждения&lt;br /&gt;
&lt;br /&gt;
Стойка (rack, cabinet) состоит из двух midplane&#039;ов. В midplane входит 16 node-карт (compute node card), на каждой из которых установлено 32 вычислительных узла (compute card). Midplane, 8 x 8 x 8 = 512 вычислительных узлов, — минимальный раздел, на котором становится доступна топология трехмерного тора; для разделов меньших размеров используется топология трехмерной решетки. Node-карта может содержать до двух узлов ввода-вывода (I/O card). Вычислительный узел включает в себя четырехъядерный процессор, 2 ГБ общей памяти и сетевые интерфейсы.&lt;br /&gt;
&lt;br /&gt;
Микропроцессорное ядро:&lt;br /&gt;
&lt;br /&gt;
* модель: PowerPC 450&lt;br /&gt;
* рабочая частота: 850 MHz&lt;br /&gt;
* адресация: 32-битная&lt;br /&gt;
* кэш инструкций 1-го уровня (L1 instruction): 32 KB&lt;br /&gt;
* кэш данных 1-го уровня (L1 data): 32 KB&lt;br /&gt;
* кэш предвыборки (L2 prefetch): 14 потоков предварительной выборки (stream prefetching): 14 x 256 байтов&lt;br /&gt;
* два блока 64-битной арифметики с плавающей точкой (Floating Point Unit, FPU), каждый из которых может выдавать за один такт результат совмещенной операции умножения-сложения (Fused Multiply-Add, FMA)&lt;br /&gt;
* пиковая производительность: 2 FPU x 2 FMA x 850 MHz = 3,4 GFlop/sec per core&lt;br /&gt;
&lt;br /&gt;
Вычислительные узлы и I/O-карты в аппаратном смысле неразличимы и являются взаимозаменяемыми, разница между ними состоит лишь в способе их использования. У них нет локальной файловой системы, поэтому все операции ввода-вывода перенаправляются внешним устройствам.&lt;br /&gt;
&lt;br /&gt;
Вычислительной узел:&lt;br /&gt;
&lt;br /&gt;
* четыре микропроцессорных ядра PowerPC 450 (4-way SMP)&lt;br /&gt;
* пиковая производительность: 4 cores x 3,4 GFlop/sec per core = 13,6 GFlop/sec&lt;br /&gt;
* пропускная способность памяти: 13,6 GB/sec&lt;br /&gt;
* 2 ГБ общей памяти&lt;br /&gt;
* 2 x 4 МБ кэш-памяти 2-го уровня (в документации по BG/P носит название L3)&lt;br /&gt;
* легковесное ядро (compute node kernel, CNK), представляющее собой Linux-подобную операционную систему, поддерживающую значительное подмножество Linux-совместимых системных вызовов&lt;br /&gt;
* асинхронные операции межпроцессорных обменов (выполняются параллельно с вычислениями)&lt;br /&gt;
* операции ввода-вывода перенаправляются I/O-картам через сеть коллективных операций&lt;br /&gt;
&lt;br /&gt;
=== Оценка масштабируемости алгоритма ===&lt;br /&gt;
Масштабируемость алгоритма проверялась с помощью тестовой программы расположенной по [https://github.com/alesapin/parallel_fftw ссылке]. Для эксперимента использовались:&lt;br /&gt;
* Компилятор mpicc&lt;br /&gt;
* Библиотека OpenMPI версии 1.68&lt;br /&gt;
* Библиотека FFTW 3.3.5&lt;br /&gt;
Алгоритм тестировался на входных векторах размера &amp;lt;math&amp;gt;2^{15}&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;2^{26}&amp;lt;/math&amp;gt;. При этом использовались от 8 до 128 процессоров с шагом степени двойки.&lt;br /&gt;
&lt;br /&gt;
[[File:Fftw time good cr.png  |center|thumb|800px| Замеры времени работы параллельной реализации FFTW для входного вектора действительных чисел. По оси Data Size указано &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; -- число входных элементов. По оси Procs Num указано &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; -- число задействованных процессоров. По оси Elapsed Time -- время затраченное на выполненине алгоритма в секундах.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Fftw efficency good cr.png |center|thumb|800px| Эффективность параллельной реализации FFTW для входного вектора действительных чисел. По оси Data Size указано &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; -- число входных элементов. По оси Procs Num указано &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; -- число задействованных процессоров. По оси Effeciency -- эффективность алгоритма в процентах.]]&lt;br /&gt;
&lt;br /&gt;
Из графиков можно сделать следующие выводы:&lt;br /&gt;
* Значительное увеличение числа процессоров не дает значимого уменьшения времени исполнения, что отражено и на графике эффективности. Это означает, что время, в основном, расходуется на пересылки данных, а не на рассчет.&lt;br /&gt;
* При большом количестве элементов и маленьком числе процессоров наблюдается сверхлинейное ускорение, что связано с маленьким число пересылок и активным использованием кеша.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Наиболее известной библиотекой для выполнения различных вариантов быстрого преобразования Фурье является [http://www.fftw.org/ FFTW], разработанная Матео Фриго и Стивеном Джонсоном в Массачусетском технологическом институте. Для преобразования небольших объемов данных используется алгоритм [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Кули - Тьюки], в случае входа больших размеров используется алгоритм [https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Радера] или [https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Блюштейна]. Библиотека считается самой быстрой реализацией БПФ и на входах любых размеров и размерностей сохраняет ассимптотическую сложность &amp;lt;math&amp;gt;O(N\cdot log(N))&amp;lt;/math&amp;gt;. Также, существует версия FFTW с поддержкой [http://www.fftw.org/doc/Distributed_002dmemory-FFTW-with-MPI.html#Distributed_002dmemory-FFTW-with-MPI MPI], которая позволяет с объемами данных не умещающимися в память.&lt;br /&gt;
&lt;br /&gt;
FFTW изначально написана на языке C, однако имеет API для нескольких языков, в том числе Fortran и Ada. Открытая версия библиотеки распространяется под лицензией [https://en.wikipedia.org/wiki/GNU_General_Public_License GNU General Public License]. Также существует реализация для коммерческого использования распространяемая под лицензией [https://en.wikipedia.org/wiki/MIT MIT].&lt;br /&gt;
&lt;br /&gt;
Существует несколько реализаций алгоритма, для рассчета на GPU:&lt;br /&gt;
* [https://developer.nvidia.com/cufft cuFFT] для рассчета на графических ускорителях от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft/ clFFT] для рассчета на GPU и CPU.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Википедия [Электронный ресурс]. Тема: Быстрое преобразование Фурье – Электрон. дан. – URL [https://ru.wikipedia.org/wiki/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5 Быстрое преобразование Фурье] (дата обращения 17.09.2016)&lt;br /&gt;
&lt;br /&gt;
[2] Бахвалов Н. С.,  Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.&lt;br /&gt;
[[en:Fast Discrete Fourier Transform]]&lt;br /&gt;
&lt;br /&gt;
[3] FFTW [Электронный ресурс]. Тема: One-Dimensional DFTs of Real Data – Электрон. дан. – URL [http://www.fftw.org/doc/One_002dDimensional-DFTs-of-Real-Data.html#One_002dDimensional-DFTs-of-Real-Data One-Dimensional DFTs of Real Data] (последняя дата обращения 14.19.2016)&lt;br /&gt;
&lt;br /&gt;
[4] FFTW [Электронный ресурс]. – Электрон. дан. – URL [http://www.fftw.org/ FFTW Lib] (последняя дата обращения 14.19.2016)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Karibov&amp;diff=17591</id>
		<title>Обсуждение участника:Karibov</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Karibov&amp;diff=17591"/>
		<updated>2016-11-16T11:06:56Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Замечания к статье ===&lt;br /&gt;
&lt;br /&gt;
* не указан конкретный вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 16:04, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:06, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Алгоритм кластеризации с использованием представлений&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* П.1.2. Мало сказано о входном наборе данных. Если это могут быть точки произвольной размерности, это надо формализовать [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:30, 11 ноября 2016 (MSK)&lt;br /&gt;
* П.1.2. Не сказано, как именно выбираются точки-представители, чтобы они были максимально удалены, это нетривиально [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:30, 11 ноября 2016 (MSK)&lt;br /&gt;
* не сказано об оптимальном выборе параметров алгоритма [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:30, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Kanast74&amp;diff=17589</id>
		<title>Обсуждение участника:Kanast74</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Kanast74&amp;diff=17589"/>
		<updated>2016-11-16T11:05:31Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Статья [[Алгоритм кластеризации, основанный на минимальном покрывающем дереве]] =&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4 дополнить текстом исследуемой программы--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:05, 16 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%9B%D1%83%D0%BA%D1%8C%D1%8F%D0%BD%D0%BE%D0%B2%D0%B0_%D0%90%D0%BD%D0%BD%D0%B0&amp;diff=17588</id>
		<title>Обсуждение участника:Лукьянова Анна</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%9B%D1%83%D0%BA%D1%8C%D1%8F%D0%BD%D0%BE%D0%B2%D0%B0_%D0%90%D0%BD%D0%BD%D0%B0&amp;diff=17588"/>
		<updated>2016-11-16T11:00:55Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Замечания к статье ===&lt;br /&gt;
&lt;br /&gt;
* содержание не соответствуют шаблону описания алгоритма: отсутствуют п.п. во 2 части--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 16:01, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:00, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Алгоритм кластеризации с использованием представлений&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* п.1.1.1. Если взялись за классификацию алгоритмов, лучше разъяснить, что такое агломеративные, дивизивные, &amp;quot;четкие&amp;quot; и &amp;quot;нечеткие&amp;quot; алгоритмы. Было бы здорово даже сделать это в отдельной статье про задачу кластеризации, но это не обязательно [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:24, 11 ноября 2016 (MSK)&lt;br /&gt;
* п.1.1.2. Метрики и некоторые подробности лучше перенести ниже, к &amp;quot;математическому описанию&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:24, 11 ноября 2016 (MSK)&lt;br /&gt;
* п.1.2 выглядит дописанным не до конца. Там нужно разъяснить те неформальные конструкции, которые встречались ранее в п. 1.1.2. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:24, 11 ноября 2016 (MSK)&lt;br /&gt;
* п.1.5. Псевдокод слишком псевдо, его следует сделать больше похожим на код, с соответствующим форматированием [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:24, 11 ноября 2016 (MSK)&lt;br /&gt;
* следует добавить информацию об оптимальном выборе параметров алгоритма [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 16:24, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:%D0%9B%D1%83%D0%BA%D1%8C%D1%8F%D0%BD%D0%BE%D0%B2%D0%B0_%D0%90%D0%BD%D0%BD%D0%B0/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D1%81_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC_%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B92&amp;diff=17587</id>
		<title>Участник:Лукьянова Анна/Алгоритм кластеризации с использованием представлений2</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:%D0%9B%D1%83%D0%BA%D1%8C%D1%8F%D0%BD%D0%BE%D0%B2%D0%B0_%D0%90%D0%BD%D0%BD%D0%B0/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D1%81_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC_%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B92&amp;diff=17587"/>
		<updated>2016-11-16T11:00:21Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[U:Лукьянова Анна|А.Я. Лукьянова]]&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмa =&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
===Понятие кластеризации===&lt;br /&gt;
&lt;br /&gt;
Кластеризация (или кластерный анализ) — это задача разбиения множества объектов на группы, называемые кластерами. Внутри каждой группы должны оказаться «похожие» объекты, а объекты разных группы должны быть как можно более отличны. Главное отличие кластеризации от классификации состоит в том, что перечень групп четко не задан и определяется в процессе работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Применение кластерного анализа в общем виде сводится к следующим этапам:&lt;br /&gt;
&lt;br /&gt;
#Отбор выборки объектов для кластеризации.&lt;br /&gt;
#Определение множества переменных, по которым будут оцениваться объекты в выборке. При необходимости – нормализация значений переменных.&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;
&#039;&#039;&#039;Методы по способу обработки данных:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Иерархические методы:&#039;&#039; строят не одно разбиение выборки на непересекающиеся кластеры, а систему вложенных разбиений. Т.о. на выходе мы получаем дерево кластеров, корнем которого является вся выборка, а листьями — наиболее мелкие кластера.&lt;br /&gt;
&lt;br /&gt;
*Агломеративные методы AGNES (Agglomerative Nesting) - характеризуются последовательным объединением исходных элементов и соответствующим уменьшением числа кластеров. В начале работы алгоритма все объекты являются отдельными кластерами. На первом шаге наиболее похожие объекты объединяются в кластер. На последующих шагах объединение продолжается до тех пор,&lt;br /&gt;
пока все объекты не будут составлять один кластер. &lt;br /&gt;
&lt;br /&gt;
*Дивизимные методы DIANA (Divisive Analysis) - характеризуются последовательным разделением исходного кластера, состоящего из всех объектов, и соответствующим увеличением числа кластеров. В начале работы алгоритма все объекты принадлежат одному кластеру, который на последующих шагах делится на меньшие кластеры, в результате образуется последовательность расщепляющих групп. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Неиерархические методы:&#039;&#039; строят одно разбиение объектов на кластеры.&lt;br /&gt;
*Итеративные&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритмы по способу анализа данных:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;Четкие&#039;&#039; (непересекающиеся) алгоритмы каждому объекту выборки ставят в соответствие номер кластера, т.е. каждый объект принадлежит только одному кластеру;&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;Нечеткие&#039;&#039; (пересекающиеся) алгоритмы каждому объекту ставят в соответствие набор вещественных значений, показывающих степень отношения объекта к кластерам. Т.е. каждый объект относится к каждому кластеру с некоторой вероятностью. &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;
&#039;&#039;&#039;Методы по возможности расширения объема обрабатываемых данных:&#039;&#039;&#039;&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;
*Потоковые (on-line);&lt;br /&gt;
&lt;br /&gt;
*Не потоковые (off-line).&lt;br /&gt;
&lt;br /&gt;
===CURE===&lt;br /&gt;
&lt;br /&gt;
Алгоритм &#039;&#039;&#039;CURE (Clustering Using REpresentatives)&#039;&#039;&#039; выполняет иерархическую кластеризацию с использованием набора определяющих точек для определения объекта в кластер. Относится к алгомеративным методам.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Назначение&#039;&#039;: кластеризация очень больших наборов числовых данных.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Достоинства&#039;&#039;: &lt;br /&gt;
*выполняет кластеризацию на высоком уровне даже при наличии выбросов;&lt;br /&gt;
*выделяет кластеры сложной формы и различных размеров, &lt;br /&gt;
*обладает линейно зависимыми требованиями к месту хранения данных и временную сложность для данных высокой размерности.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Недостатки&#039;&#039;: есть необходимость в задании пороговых значений и количества кластеров. &lt;br /&gt;
&lt;br /&gt;
Описание алгоритма :&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[file:Page1.png|thumb|center|1400px|Рисунок 1: Обзор CURE ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 1:&#039;&#039;&#039; Построение дерева кластеров, состоящего из каждой строки входного набора данных.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 2:&#039;&#039;&#039; Формирование «кучи» в оперативной памяти, расчет расстояния до ближайшего кластера (строки данных) для каждого кластера. При формировании кучи кластеры сортируются по возрастанию дистанции от кластера до ближайшего кластера. Расстояние между кластерами определяется по двум ближайшим элементам из соседних кластеров. Для определения расстояния между кластерами используются «манхеттенская», «евклидова» метрики или похожие на них функции.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 3:&#039;&#039;&#039; Слияние ближних кластеров в один кластер. Новый кластер получает все точки входящих в него входных данных. Расчет расстояния до остальных кластеров для новообразованного кластера. Для расчета расстояния кластеры делятся на две группы: первая группа – кластеры, у которых ближайшими кластерами считаются кластеры, входящие в новообразованный кластер, остальные кластеры – вторая группа. И при этом для кластеров из первой группы, если расстояние до новообразованного кластера меньше чем до предыдущего ближайшего кластера, то ближайший кластер меняется на новообразованный кластер. В противном случае ищется новый ближайший кластер, но при этом не берутся кластеры, расстояния до которых больше, чем до новообразованного кластера. Для кластеров второй группы выполняется следующее: если расстояние до новообразованного  кластера ближе, чем предыдущий ближайший кластер, то ближайший кластер меняется. В противном случае ничего не происходит.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 4:&#039;&#039;&#039; Переход на шаг 3, если не получено требуемое количество кластеров.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Прежде всего, алгоритм использует случайное разбиение выборки из &amp;lt;math&amp;gt; s &amp;lt;/math&amp;gt; элементов на &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; частей, которые могут поместиться в память отдельной машины кластера. Затем во время первого прохода алгоритм уменьшает число кластеров в каждой подвыборке до &amp;lt;math&amp;gt; \frac{s}{pq} &amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt; q \geq 1 &amp;lt;/math&amp;gt; – некоторая константа (происходит объединение ближайших кластеров). Для определения расстояния между кластерами используются «манхеттенская», «евклидова» метрики или похожие на них функции.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Метрика Евклида:&#039;&#039; &amp;lt;math&amp;gt;\rho(p,k) = \sqrt{\sum_{i=1}^n (p_{i} - k_{i})^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;«Манхеттенская» метрика:&#039;&#039; &amp;lt;math&amp;gt;\rho(p,k) =\sum_{i=1}^n |p_{i} - k_{i}|&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если расстояние между ближайшей парой кластеров больше некоторого парогового(критического) значения, то объединения не происходит.&lt;br /&gt;
 &lt;br /&gt;
Для последующего шага на вход кластеризации подаются только представительные точки &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;  кластеров, полученных на предыдущем этапе. В ходе второго прохода алгоритма кластеризации остаются представительные точки для заданного количества кластеров. Алгоритм использует представительные точки, как метки, задающие форму и размер кластера.&lt;br /&gt;
&lt;br /&gt;
Число точек представителей выбирается в зависимости от данных. Если возможно появление кластеров сложной формы, то точек должно быть больше, чтобы выявить их форму. В оригинальной статье авторы показывают что уже при &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; равном &amp;lt;math&amp;gt;10&amp;lt;/math&amp;gt; достигается неплохая эффективность.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Param_c.png|400px|thumb|center| Рисунок 2: Результат работы алгоритка в зависимости от количесва представлений &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Из рисунка видно, что при меньших значениях &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; качество работы алгоритма ухудшается&lt;br /&gt;
&lt;br /&gt;
Выбранные точки сдвигаются на &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; к центроиду кластера. Алгоритм становится основанным на методе поиска центроида при &amp;lt;math&amp;gt;\alpha = 1&amp;lt;/math&amp;gt;, и основанным на всех точках кластера при &amp;lt;math&amp;gt;\alpha = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Factor_a.png|400px|thumb|center| Рисунок 3: Чувствительность алгоритма к параметру &amp;lt;math&amp;gt;\alpha &amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма представляет собой итерационное построение &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров, вычисление их центроид и назначение точек-представителей.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основную часть метода составляют разбиение случайной выборки строк на &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров и нахождение точек-представителей для каждого кластера.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Входные параметры: набор входных данных &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, содержащий &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; точек в &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-мерном пространстве&lt;br /&gt;
и требуемое количество кластеров &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;. Начиная с отдельных точек в виде отдельных кластеров, на каждом шаге ближайшая пара кластеров объединяются, чтобы сформировать новый кластер.Процесс повторяется до тех пор, пока не достигнет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;procedure&#039;&#039;&#039; cluster(S,k)&lt;br /&gt;
 &#039;&#039;&#039;begin&#039;&#039;&#039;&lt;br /&gt;
 1. T:=build_kd_tree(S)&lt;br /&gt;
 2. Q:-build_heap(S)&lt;br /&gt;
 3. &#039;&#039;&#039;while&#039;&#039;&#039; size(Q)&amp;gt;k &#039;&#039;&#039;do&#039;&#039;&#039; {&lt;br /&gt;
 4.     u:=extract_min(Q)&lt;br /&gt;
 5.     v:= u.closest&lt;br /&gt;
 6.     delete(Q, V)&lt;br /&gt;
 7.     w := merge(u,v) &lt;br /&gt;
 8.     delete_rep(T, u); delete_rep(T, v); insert_rep(T, w) &lt;br /&gt;
 9.     w.closest := x /* x is an arbitrary cluster in Q */&lt;br /&gt;
 10.    &#039;&#039;&#039;for each&#039;&#039;&#039; x \in Q&#039;&#039;&#039;do&#039;&#039;&#039; {&lt;br /&gt;
 11.       &#039;&#039;&#039;if&#039;&#039;&#039; dist(w, x) &amp;lt; dist(w, w.closest) &lt;br /&gt;
 12.               w.closest := x &lt;br /&gt;
 13.       &#039;&#039;&#039;if&#039;&#039;&#039; x.closest is either u or w {  &lt;br /&gt;
 14.            &#039;&#039;&#039; if&#039;&#039;&#039; dist(x, x.closest) &amp;lt; dist(x, w)&lt;br /&gt;
 15.                 x.closest := closest_cluster(T, x, dist(x, w)) &lt;br /&gt;
 16.            &#039;&#039;&#039;else&#039;&#039;&#039;&lt;br /&gt;
 17.               x.closest := w&lt;br /&gt;
 18.             relocate(Q, x) &lt;br /&gt;
 19.        }&lt;br /&gt;
 20.       &#039;&#039;&#039;else if&#039;&#039;&#039; dist(x, x.closest) &amp;gt; d&amp;amp;(x, w) { &lt;br /&gt;
 21.             x.closest := w &lt;br /&gt;
 22.             relocate(Q, x)&lt;br /&gt;
 23.        }&lt;br /&gt;
 24.      }&lt;br /&gt;
 25.      insert(Q, w)&lt;br /&gt;
 26. }&lt;br /&gt;
 &#039;&#039;&#039;end&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Являясь представителем иерархических алгоритмов кластеризации, имеет сложность &amp;lt;math&amp;gt; O(n^2 log(n)) &amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt; O(n^2) &amp;lt;/math&amp;gt; для признаковых пространств малой размерности &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма все объекты являются отдельными кластерами. На первом шаге наиболее похожие объекты объединяются в кластер. На последующих шагах объединение продолжается до тех пор, пока все объекты не будут составлять один кластер.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Metod.png|400px|thumb|center| Рисунок 4: Дендрограмма агломеративных методов]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Т.к все действия алгорима (вычисления центроид для кластеров, расстояния между кластерами, представители кластеров и изменение положения представителей) на каждом шаге можно делать независимо (как от представителей данного кластера, так и представителей других кластеров), можно сделать вывод, что возможно распараллеливание&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; набор данных &amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; , количество кластеров &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;, число точек представителей &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, параметр &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, параметр &amp;lt;math&amp;gt;q&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; число разбиений выборки, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; размерность.&lt;br /&gt;
&lt;br /&gt;
Параметр &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; для сжатия точек представления к центру кластера.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; требуемое количество &amp;lt;math&amp;gt; k &amp;lt;/math&amp;gt; крастеров&lt;br /&gt;
&lt;br /&gt;
===Оптимальный выбор параметров для алгоритма===&lt;br /&gt;
&lt;br /&gt;
*размер случайной выборки &amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; (&#039;&#039;а&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
При запуске CURE со случайными размерами выборки в пределах от 500 до 5000 видно, что при размерах до 2000 были найдены кластеры низкого качества, начиная с 2500 точек и выше (2,5% от установленного размера данных), CURE всегда правильно определяет кластеры.&lt;br /&gt;
&lt;br /&gt;
*число точек представителей  &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; (&#039;&#039;b&#039;&#039;)&lt;br /&gt;
В оригинальной статье авторы показывают что уже при &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; равном &amp;lt;math&amp;gt;10&amp;lt;/math&amp;gt; достигается неплохая эффективность.&lt;br /&gt;
&lt;br /&gt;
*число разбиений выборки &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; (&#039;&#039;&#039;c&#039;&#039;&#039;)&lt;br /&gt;
&amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; должно быть выбрано таким, что &amp;lt;math&amp;gt; \frac{s}{pq} &amp;lt;/math&amp;gt; &amp;gt; &amp;lt;math&amp;gt; k &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
*размерность (&#039;&#039;d&#039;&#039;)&lt;br /&gt;
CURE хорошо себя чувствует при больших размерностях&lt;br /&gt;
&lt;br /&gt;
*сжимающий фактор &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; &lt;br /&gt;
В оригинальной статье авторы показывают что  &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; от 0.2 до 0.7 помогает хорошо выявлять несферические кластеры и подавлять редкие выбросы.&lt;br /&gt;
&lt;br /&gt;
[[Файл:4pic.png|800px|thumb|center| Рисунок 5: Влияние параметров &amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt; p &amp;lt;/math&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;br /&gt;
&lt;br /&gt;
будет..&lt;br /&gt;
&lt;br /&gt;
==Динамические характеристики и эффективность реализации алгоритма==&lt;br /&gt;
&lt;br /&gt;
==Выводы для классов архитектур==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Последовательная реализация CURE-алгоритма для кластеризации для Python/C++ представлена в пакете: [https://github.com/annoviko/pyclustering pyclustering].&lt;br /&gt;
&lt;br /&gt;
Также существует параллельная реализация алгоритма, описанная в сборнике [4]. Алгоритм предложенный в статье использует массив данных состоящий из размеров кластера, его центроида и репрезентативных точек, а при объединении кластеров информацию о новом кластере просто записывают в строчку предыдущего. Также для каждого кластера требуется считать индекс и расстояние до ближайшего кластера. Именно их поиск авторы статьи и предлагают распараллелить.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
[1] Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. - СПб.: БХВ-Петербург, 2002. - 608 с.&lt;br /&gt;
&lt;br /&gt;
[2] Theodoridis, Sergios; Koutroumbas, Konstantinos (2006). Pattern recognition. Academic Press. pp. 572–574. ISBN 978-0-12-369531-4.&lt;br /&gt;
&lt;br /&gt;
[3] Guha, Sudipto; Rastogi, Rajeev; Shim, Kyuseok (2001). &amp;quot;CURE: An Efficient Clustering Algorithm for Large Databases&amp;quot; (PDF). Information Systems. 26 (1): 35–58. doi:10.1016/S0306-4379(01)00008-4&lt;br /&gt;
&lt;br /&gt;
[4] Panagiotis E. Hadjidoukas, Laurent Amsaleg. OpenMP Shared Memory Parallel Programming.&lt;br /&gt;
Volume 4315 of the series Lecture Notes in Computer Science pp 289-299&lt;br /&gt;
Parallelization of a Hierarchical Data Clustering Algorithm Using OpenMP&lt;/div&gt;</summary>
		<author><name>IgorS</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=17586</id>
		<title>Участник:Kanast74/Алгоритм кластеризации, основанный на минимальном покрывающем дереве</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=17586"/>
		<updated>2016-11-16T10:59:19Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Статью подготовила: [[Участник:kanast74|Дмитриева Анастасия Владимировна]].&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&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;X=\{x_1...x_n\}&amp;lt;/math&amp;gt;, а также функция расстояния между ними &amp;lt;math&amp;gt;r(e)&amp;lt;/math&amp;gt;. Отметим, что метрика, в которой вычисляются расстояния между объектами, выбирается исходя из требований задачи; наиболее распространенные - это Евклидово расстояние, квадрат Евклидова расстояния, манхэттенская метрика, расстояние Чебышева и степенное расстояние. Далее необходимо применить один из алгоритмов кластеризации, которые можно разделить на две категории: иерархические, состоящие в последовательном поиске кластеров из уже найденных кластеров, а также неиерархические, цель которых состоит в минимизации некоторой целевой функции. К последней группе относятся такие алгоритмы, как алгоритм k-средних, алгоритм EM, нечеткие алгоритмы, а также алгоритмы теории графов, к категории которых относится алгоритм, описываемый в данной статье.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим алгоритм кластеризации на k кластеров, основанный на построении минимального остовного дерева (MST) и который состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
# По исходным данным строится взвешенный неориентированный граф: каждый объект представляется в виде вершины графа, а вес ребра, связывающего две вершины, есть расстояние между объектами в выбранной заранее метрике&lt;br /&gt;
# Для полученного графа строится минимальное остовное дерево, то есть дерево, содержащее все вершины графа и имеющее минимальный суммарный вес своих ребер&lt;br /&gt;
# Удаляется k-1 ребро минимального остовного дерева с максимальным весом&lt;br /&gt;
# Получаются k компонент связности, которые интерпретируются как кластеры&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;nowiki&amp;gt;, наиболее подробно изучить который можно, пройдя по ссылке [&amp;lt;/nowiki&amp;gt;[https://algowiki-project.org/ru/Алгоритм_Крускала Алгоритм Крускала]], поэтому в данной статье его описание не приводится.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
С математической точки зрения задачу кластеризации можно рассматривать как минимизацию среднего внутрикластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} 1} \to min &amp;lt;/math&amp;gt; с одной стороны и максимизации межкластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)\neq c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i) \neq c(x_j)} 1} \to max &amp;lt;/math&amp;gt; с другой стороны, где &amp;lt;math&amp;gt;c(x)&amp;lt;/math&amp;gt; - кластер, к которому принадлежит объект &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Применительно к алгоритмам кластеризации, основанным на теории графов, процедура минимизации/максимизации соответствующих расстояний состоит в выборе определенного порогового значения веса ребра &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;, ребра с весом меньше которого предполагаются связующими для вершин, принадлежащих одному классу, а ребра с весом больше которого предполагаются связующими для вершин, принадлежащих разным классам.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром описываемого алгоритма является построение минимального остовного дерева. В случае, если программа предполагается быть распараллеленной, то операция построения MST будет производиться в отдельности в каждом процессе. Как уже было отмечено, в данном исследовании для построения MST использовался алгоритм Крускала, который будет работать наиболее эффективно, если веса ребер исходного графа отсортированы, поэтому сортировка - это еще одна операция, которая должна быть включена в вычислительное ядро, что предполагает выбранная ревлизация алгоритма. Поэтому в дальнейшем мы не будем разделять сортировку и поиск минимального остовного дерева, предполагая, что они объеденены в единый эффективный алгоритм. &lt;br /&gt;
&lt;br /&gt;
Дадим некоторую справку касательно адгоритма поиска MST, фигурирующего в данном исследовании. Алгоритм Крускала — алгоритм построения минимального остовного дерева взвешенного связного неориентированного графа. Алгоритм впервые описан Джозефом Крускалом в 1956 году.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Крускала находит безопасное ребро для добавления в растущий лес путем поиска ребра &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; с минимальным весом среди всех ребер, соединяющих два дерева в лесу. Обозначим два дерева, соединяемые ребром &amp;lt;math&amp;gt;(u, v) &amp;lt;/math&amp;gt;, как &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; — безопасное для &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; ребро. Алгоритм Крускала является жадным, поскольку на каждом шаге он добавляет к лесу ребро с минимально возможным весом.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
# Построение графа путем задания списков ребер. В общем случае, когда задано некоторое множество объектов, для построения графа может быть использован алгоритм, позволяющий рассчитать расстояние от каждого объекта до каждого за &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; число объектов (вообще говоря, данная операция требует &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; - число ребер в графе, но в нашем случае число ребер подразумевается равным &amp;lt;math&amp;gt;E=\frac{D (n(n-1))}{2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - плотность графа). Однако в рамках данного исследования для тестирования алгоритма использовалась такая технология построения графов, когда в качестве веса каждого ребра назначается некоторое произвольное число из заданного промежутка (например, от 1 до 1000), что также требует &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
# Построение минимального остовного дерева на основании полученного ранее графа, для чего может быть использован один из известных и наиболее распространенных алгоритмов (алгоритм Крускала, алгоритм Борувки или алгоритм Прима). Рассмотрим, например, алгоритм Крускала. На начальном этапе имеется граф, состоящий из вершин - объектов исходной выборки и не содержащий ребер, следовательно, каждая из вершин изначально представляет собой компоненту связности и даже дерево. Алгоритм состоит в выборе на каждом шаге ребра с минимальным весом и добавлении его в дерево, если оно соединяет два дерева. Алгоритм будет работать максимально эффективно, если изначально ребра графа, полученного на первом шаге будут отсортированы (например, по возрастанию). &lt;br /&gt;
# Для получения &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров необходимо удалить из получившегося минимального остовного дерева &amp;lt;math&amp;gt;k-1&amp;lt;/math&amp;gt; ребро с максимальным весом. В частности, в случае, если на предыдущем шаге мы пользовались алгоритмом Крускала для построения MST, ребра получившегося дерева отсортированы по возрастанию, значит для удаления указанных выше ребер потребуется лишь &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
# Наконец, необходимо правильно интерпретировать результат работы алгоритма и перечислить вершины, принадлежащие полученным кластерам; на выполнение данного шага может потребоваться различное число операций в зависимости от формата выходных данных программы: например, в данной реализации выходные данные, как будет сказано в дальнейшем, представлены в виде списка ребер, формирующий итоговый минимальный остовный лес, следовательно в общем случае на его обработку потребуется &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
==== Используемые обозначения и структуры данных ====&lt;br /&gt;
&lt;br /&gt;
Обозначим в качестве &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; граф, содержащий &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; вершин и &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; ребер. Для построения минимального остовного дерева для данного графа используются три операции [2], опишем здесь каждую из них:&lt;br /&gt;
&lt;br /&gt;
# операция MakeSet(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) генерирует дерево (множество) для вершины графа &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, являющейся корнем искомого дерева&lt;br /&gt;
# операция Find(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) определяет, которому из деревьев принадлежит интересующая вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Условимся, что данная операция возвращает ту вершину, которая является корнем искомого дерева&lt;br /&gt;
# операция Union(&amp;lt;math&amp;gt;u, v&amp;lt;/math&amp;gt;) объединяет два множества (два дерева, содержащих соответствующие вершины) под одним корнем&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма === &lt;br /&gt;
&lt;br /&gt;
Приведем здесь псевдокод основной части алгоритма - а именно построения локального минимального остовного дерева при помощи алгоритма Крускала.&lt;br /&gt;
&lt;br /&gt;
Kruskal ( G=(V,E) ):    &lt;br /&gt;
&lt;br /&gt;
1. mst = ∅;    &lt;br /&gt;
&lt;br /&gt;
2. сортировать E по возрастанию;&lt;br /&gt;
&lt;br /&gt;
3. для каждой вершины v из множества вершин V:&lt;br /&gt;
&lt;br /&gt;
4. MakeSet(v);&lt;br /&gt;
&lt;br /&gt;
5. для каждого ребра e = (u, v) из множества ребер E:&lt;br /&gt;
&lt;br /&gt;
6.    if Find(u) ≠ Find(v):&lt;br /&gt;
&lt;br /&gt;
7.    mst = mst ∪ { (u,v) }; &lt;br /&gt;
&lt;br /&gt;
8.    Union(u, v); &lt;br /&gt;
&lt;br /&gt;
9. return mst;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Сперва оценим последовательную сложность составных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
0. O(E): Формирование графа&lt;br /&gt;
&lt;br /&gt;
1. O(1): Инициализация пустого множества. &lt;br /&gt;
&lt;br /&gt;
2. O(Elog(E)): сортировка ребер из множества E. &lt;br /&gt;
&lt;br /&gt;
3. O(V): Инициализация одноточечного множества для каждой вершины из V. &lt;br /&gt;
&lt;br /&gt;
4.    ▲ MakeSet за O(1). &lt;br /&gt;
&lt;br /&gt;
5. O(Elog(V)): Операции Union и Find для каждого ребра из E. &lt;br /&gt;
&lt;br /&gt;
6.    ▲ Find за O(1). &lt;br /&gt;
&lt;br /&gt;
7.    ▲ Присоединение элемента за O(1). &lt;br /&gt;
&lt;br /&gt;
8.    ▲ Union за O(log(V)).&lt;br /&gt;
&lt;br /&gt;
9. O(1): Удаление k-1 ребра для получения k компонент связности&lt;br /&gt;
&lt;br /&gt;
10. O(V): Перечисление вершин, входящих в соответствующие кластеры&lt;br /&gt;
&lt;br /&gt;
11. O(1): Return.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учетом того, что, получаем:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T( G = (V, E) ) = O(1) + O(E\log(E)) + O(V) + O(E\log(V)) + O(E) = O(E\log(E)) + O(E\log(V))  = O(E\log(V)) + O(E\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Итак, оценка сложности алгоритма есть &amp;lt;math&amp;gt;T( G = (V, E) ) = O(E\log(V)) + O(\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
[[file:Clustering kruskal.png|thumb|center|1400px|Рисунок 1. Информационный граф алгоритма]]&lt;br /&gt;
&lt;br /&gt;
По структуре информационного графа можно сделать вывод что высота канонической ярусно-параллельной формы [1] есть &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; число процессов.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Описанный алгоритм позволяет произвести распараллеливание, источник которого будет описан в данном пункте. Прежде всего, необходимо отметить, что данный алгоритм допускает преобразование в гибридную параллельную версию, а именно версию с использованием технологий MPI и OpenMP. &lt;br /&gt;
&lt;br /&gt;
Тем не менее, прежде всего следует установить справедливость свойства используемого алгоритма поиска MST, позволяющего произвести его распараллеливание [3]: &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ассоциативность по рёбрам&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;MSF(E)&amp;lt;/math&amp;gt; – минимальный остовный лес графа с рёбрами &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;. Тогда&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        MSF(E_1 \cup E_2 \cup \dots \cup E_k) = MSF(MSF(E_1) \cup MSF(E_2) \cup \dots \cup MSF(E_k)).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В том случае, когда все объекты заранее известны, и между ними можно рассчитать все попарные расстояния, первый шаг - построение графа - позволяет произвести распараллеливание при помощи обеих вышеуказанных технологий, а именно: разделить объекты между процессами при помощи MPI, а внутри каждой из полученных групп будем рассчитывать попарные расстояния параллельно на нескольких ядрах, использовав технологию OpenMP. Однако в данном исследовании кластеризуемые объекты предполагались заранее неизвестными, так как попарные расстояния между объектами задаются случайным образом, а это означает, что произвести распараллеливание этого шага, которое бы дало хотя бы небольшое ускорение (уменьшение времени работы), не получится.&lt;br /&gt;
&lt;br /&gt;
Если все же объекты заранее были известны, можно сохранить полученную на предыдущем этапе группировку их на отдельные процессы и внутри каждого произвести сортировку расстояний между объектами по возрастанию при помощи алгоритма QSort с использованием технологии OpenMP (в противном случае параллельная структура алгоритма поддерживается начиная с этого шага).&lt;br /&gt;
&lt;br /&gt;
Далее внутри каждой группы объектов (каждый процесс в отдельности) строим минимальное остовное дерево, в конце шага деревья объединяются в одно - общее MST для всех объектов, поддерживая при этом отсортированность расстояний по возрастанию.&lt;br /&gt;
&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  &amp;lt;math&amp;gt; \log{p} &amp;lt;/math&amp;gt; шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
==== Способы разделения графа ====&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;G = (V, E)&amp;lt;/math&amp;gt;, являющийся неориентированным, взвешенным, связным и не имеющим кратных ребер. Пусть полученный граф содержит &amp;lt;math&amp;gt; |V|&amp;lt;/math&amp;gt; вершин и  &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; ребер. Предполагая, что каждый объект можно сравнить с каждым, можно считать, что получаемый граф является полным, а значит число ребер может быть оценено следующим образом: &amp;lt;math&amp;gt;|E|=\frac{|V|(|V|-1)}{2} \le \frac{|V|^2}{2}-\frac{|V|}{2} \Rightarrow O(E)=O(V^2)&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Обозначим в виде &amp;lt;math&amp;gt;w(u,v)&amp;lt;/math&amp;gt; вес ребра, соединяющего вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Тогда граф может быть задан в виде списка ребер, а именно списка троек: &amp;lt;math&amp;gt;(u,v,w(u,v))&amp;lt;/math&amp;gt;, что является более удобным представлением графа для реализации многих алгоритмов по сравнению с матрицей смежности. Помимо этого, данный способ помогает сэкономить память. Недостатков указанного подхода является только то, что установить, с какими вершинами связана интересующая вершина, а также вес соответствующих ребер является весьма затруднительно. Однако для решения поставленной задачи нам это и не потребуется.&lt;br /&gt;
&lt;br /&gt;
Формат выходных данных аналогичный&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
==== Вычислительная мощность ====&lt;br /&gt;
Оценим вычислительную мощность алгоритма в том случае, когда для его реализации используется &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов. Предположим, что алгоритм реализован при помощи первой стратегии (см. выше) и применяется к графу &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt;, тогда:&lt;br /&gt;
&lt;br /&gt;
* Ввод данных: &amp;lt;math&amp;gt;\frac{|E|}{p}&amp;lt;/math&amp;gt; - в среднем столько ребер содержит получаемый каждым процессом связный подграф&lt;br /&gt;
* Вывод данных: &amp;lt;math&amp;gt;\frac{|V| - 1}{p}&amp;lt;/math&amp;gt; - каждый из процессов должен вывести посчитанное локальное минимальное дерево, которое содержит указанное количество ребер, так как остальные ребра, изначально входившие в состав подграфа, не будут входит в локальное MST&lt;br /&gt;
* Вычисление: для вычисления локального MST каждому из процессов требуется &amp;lt;math&amp;gt;\frac{|E|}{p} \log{\frac{|V|}{p}}&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
Таким образом, искомое соотношение может быть записано в следующем виде: &amp;lt;math&amp;gt;\frac{\frac{|E|}{p} \log{\frac{|V|}{p}}}{\frac{|E|}{p} + \frac{|V| - 1}{p}} = \frac{|E| \log{\frac{|V|}{p}}}{|E|+|V|-1}&amp;lt;/math&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;
В данном рзеделе будет производиться оценка прироста производительности параллельного алгоритма по сравнению с последовательным. Наиболее простой способ оценить прирост производительности – использовать закон Амдала, который гласит, что прирост может быть оценен следующим образом: &amp;lt;math&amp;gt; S_p \le \frac{1}{f+\frac{1-f}{p}}&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; – часть алгоритма, которая не может быть распараллелена, &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; – число процессов.&lt;br /&gt;
&lt;br /&gt;
В наиболее тривиальном случае (первая стратегия распараллеливания) распараллеливанию не подлежит считывание входных данных и их разделение между процессами, а также объединение локальных MST в одно. Поэтому сложность последовательной части программы может быть оценена следующим образом: &amp;lt;math&amp;gt;2O(V^2)+O(V^2 \log{V})&amp;lt;/math&amp;gt;. Распараллеливанию подлежит инициализация подграфов при помощи операции MakeSet за &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt;, сортировка ребер по возрастанию внутри каждой группы за &amp;lt;math&amp;gt;O(V^2 \log{V})&amp;lt;/math&amp;gt;, а также построение локальных минимальных остовных деревьев за аналогичное время. Поэтому сложность параллельной части алгоритма может быть оценена так:  &amp;lt;math&amp;gt;O(V)+2 O(V^2 \log{V})&amp;lt;/math&amp;gt;. Таким образом, вычислив отношение сложностей частей программы, получаем, что  &amp;lt;math&amp;gt;f=0.44&amp;lt;/math&amp;gt;. Устремим число процессов  &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; к бесконечности и получим, что максимальное ускорение, которое можно получить путем распараллеливания алгоритма есть &amp;lt;math&amp;gt;2.27&amp;lt;/math&amp;gt;. К сожалению, данная оценка не является точной. Для того, чтобы оценить прирост производительности более качественно, произведем анализ временных диаграмм программы.&lt;br /&gt;
&lt;br /&gt;
===== Анализ временных диаграмм =====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;t_d&amp;lt;/math&amp;gt; – время, которое требуется для того, чтобы разделить граф на подграфы, &amp;lt;math&amp;gt;t_p&amp;lt;/math&amp;gt; – суммарное время вычислений, &amp;lt;math&amp;gt;t_b&amp;lt;/math&amp;gt; – время обратной пересылки локально полученных результатов, &amp;lt;math&amp;gt;t_l&amp;lt;/math&amp;gt; – задержка одной пересылки. Причем можно убедиться в том, что &amp;lt;math&amp;gt;t_p &amp;gt; t_d &amp;gt; t_b &amp;gt; t_l&amp;lt;/math&amp;gt;, также известно, что время выполнения последовательного кода &amp;lt;math&amp;gt;t_{serial} = t_p&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:FirstStrategy.PNG|thumb|center|400px|Рисунок 2. Первая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Произведем анализ первой (наивной) стратегии распараллеливания программы. На временной диаграмме жирным обозначен критический путь, время выполнения которого может быть вычислено по формуле (см: [2]):&lt;br /&gt;
&amp;lt;math&amp;gt;t_{paralel} = 4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда максимальное ускорение для одного процесса есть: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{t_p}{4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А в случае n процессов: &amp;lt;math&amp;gt;speedup = \frac{n}{1+\frac{n (n+1) t_l + n t_d + t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:SecondStrategy.PNG|thumb|center|400px|Рисунок 3. Вторая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Теперь проанализируем вторую стратегию, основная задача которой – уменьшить объем коммуникаций между процессами за счет того, что файл с исходными данными теперь доступен всем процессам. Помимо этого введем в рассмотрение новую величину &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; – время, требующееся на инициализацию множеств вершин. Тогда для одного процесса &amp;lt;math&amp;gt;t_{parallel} = t_i + \frac{t_p}{4} + t_l + 3 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а максимальное ускорение в случае n процессов есть: &amp;lt;math&amp;gt;speedup= \frac{t_{serial}}{t_{parallel}} = \frac{4}{1+\frac{4 t_i + 4 t_l + 3 t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:ThirdStrategy.PNG|thumb|center|400px|Рисунок 4. Третья стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Наконец, проанализируем третью стратегию, основная цель которой – оптимизировать процесс слияния локальных минимальных остовных деревьев, а также привлечь все активные процессы (включая главный) к процессу построения локального MST. В этом случае время выполнения параллельной части программы с использованием лишь одного процесса: &amp;lt;math&amp;gt; t_{parallel}=t_i + \frac{t_p}{4} + t_l + 2 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а суммарное ускорение в общем случае: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{n}{1+\frac{n t_i + n t_l + \log_2{n} t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На рисунке 5 приведены графики, отражающие зависимость получаемых возможных ускорений в зависимости от числа задействованных процессов, посчитанные с использованием четырех различных подходов.&lt;br /&gt;
[[Файл:Comparison.PNG|thumb|center|400px|Рисунок 5. Сравнение стратегий распараллеливания]]&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
[[file:Total.png|thumb|center|400px|Рисунок 6. График зависимости времени вычисления от размера задачи и числа процессов (с учетом времени ввода-вывода)]]&lt;br /&gt;
[[file:Wo_io.png|thumb|center|400px|Рисунок 7. График зависимости времени вычисления от размера задачи и числа процессов (без учета времени ввода-вывода)]]&lt;br /&gt;
&lt;br /&gt;
На графиках видно, как с увеличением объема вычислительных ресурсов происходит прирост в производительности. Данный эффект происходит до некоторых пор, после чего накладные расходы на коммуникацию между процессами нивелируют его. При дальнейшем увеличении числа процессов время решения задачи заметно увеличивается, однако, ввиду длинных очередей на суперкомпьютере Ломоносов данные с числом процессов &amp;lt;math&amp;gt; \ge 256&amp;lt;/math&amp;gt; получить не удалось&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма  [3]===&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/kruskal_min_spanning_tree.html kruskal_min_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#merge-local-minimum-spanning-trees merge_local_minimum_spanning_trees]&amp;lt;/code&amp;gt; реализует алгоритм Крускала;&lt;br /&gt;
** функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают [[алгоритм Борувки]] и алгоритм Крускала.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.mst.minimum_spanning_tree.html minimum_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/KruskalMinimumSpanningTree.html KruskalMinimumSpanningTree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. - СПб.: БХВ-Петербург, 2002. - 608 с.&lt;br /&gt;
&lt;br /&gt;
[2] http://www.gpiskas.com/pdf/kruskal_mpi.pdf (дата обращения: 02.10.2016)&lt;br /&gt;
&lt;br /&gt;
[3] https://algowiki-project.org/ru/Алгоритм_Крускала&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Kanast74&amp;diff=17585</id>
		<title>Обсуждение участника:Kanast74</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Kanast74&amp;diff=17585"/>
		<updated>2016-11-16T10:58:34Z</updated>

		<summary type="html">&lt;p&gt;IgorS: Новая страница: «= Статья Алгоритм кластеризации, основанный на минимальном покрывающем дереве =  * п.п. 2…»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Статья [[Алгоритм кластеризации, основанный на минимальном покрывающем дереве]] =&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4 дополнить текстом исследуемой программы&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Vagabond&amp;diff=17579</id>
		<title>Обсуждение участника:Vagabond</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Vagabond&amp;diff=17579"/>
		<updated>2016-11-16T10:52:52Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Замечания к статье ===&lt;br /&gt;
&lt;br /&gt;
* содержание не соответствуют шаблону описания алгоритма: неверная нумерация, отсутствуют п.п.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:30, 25 октября 2016 (MSK)   &lt;br /&gt;
* не указан конкретный вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:30, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 1.9 не раскрыт--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:30, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 2.4 дополнить кодом исследуемой программы--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:52, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Алгоритм быстрого дискретного преобразования Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* Раздел 1.1 - вместо общего описания алгоритма написано именно что математическое описание. В общем описании должна быть поставлена задача и изложена основная идея, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 14:08, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2 - не очень ясны слова про &amp;quot;проиллюстрированные на рисунке 2 в разделе Информационный граф&amp;quot;. Раздел &amp;quot;Информационный граф&amp;quot; содержит рисунок 1. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 14:08, 11 ноября 2016 (MSK)&lt;br /&gt;
* Если упоминаем рекурсию, нужны слова о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 14:08, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17578</id>
		<title>Участник:Vagabond/Алгоритм быстрого дискретного пребразования Фурье</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17578"/>
		<updated>2016-11-16T10:51:27Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&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;
Главная идея БПФ состоит в сокращении многочисленных повторов сложения и умножения, которые выполняются в ДПФ (повторяющиеся множители: &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; Например при условии, что  &amp;lt;math&amp;gt;(pk)mod(n)=const&amp;lt;/math&amp;gt;, при всех &amp;lt;math&amp;gt;k=\overline{0,n-1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;p=\overline{0,n-1}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Напомним формулу ДПФ: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2kpi\pi}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
За счет использования данного факта, происходит значительное сокращение количества вычислительных операций. В данной статье используется алгоритм Cooley – Tukey, который предполагает собой разбиение исходного множества входных сигналов на два равных подмножества. При этом множество входных сигналов должно быть степенью двойки. Далее операция деления множества продолжается для образовавшихся подмножеств, и так далее. В результате над каждым из полученных подмножеств выполняется БПФ. Данные вычисления можно выполнять независимо друг от друга. Этот факт используется для реализации параллельной версии алгоритма. В общем алгоритм Cooley – Tukey строится из двух основных шагов:&lt;br /&gt;
&lt;br /&gt;
* Преобразование входного массива данных (бит-реверсирование).&lt;br /&gt;
* Вычисление БПФ.&lt;br /&gt;
Приведем более детальное описание БПФ (математическое описание):&lt;br /&gt;
*Пусть &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\omega _{n}^{kp}&amp;lt;/math&amp;gt;.&lt;br /&gt;
*Разобьем исходный сигнал &amp;lt;math&amp;gt;x(n)&amp;lt;/math&amp;gt; на два подмножества, с четными &amp;lt;math&amp;gt;x_{1}(2n)&amp;lt;/math&amp;gt; и нечетными &amp;lt;math&amp;gt;x_{2}(2n+1)&amp;lt;/math&amp;gt; номерами.&lt;br /&gt;
Тогда получаем: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}\omega _{n}^{kp}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{(2k+1)p}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выпишем два свойства омеги:&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{\frac{n}{2}}=e^{2(\frac{-2i\pi}{n})}=\omega_{n}^{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{n}^{\frac{n}{2}}=e^{\frac{-2ni\pi}{2n}}=e^{-i\pi}=-1&amp;lt;/math&amp;gt;&lt;br /&gt;
Воспользуемся первым: &amp;lt;math&amp;gt;y_{p}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{2kp}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;. Или, если обозначить &amp;lt;math&amp;gt;y_{1}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;y_{2}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, то получим формулу для вычисления координат изображения БПФ при &amp;lt;math&amp;gt;p=\overline{0...n/2}&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;y_(p)=y_{1}(p)+\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заметим, что &amp;lt;math&amp;gt;y(p); y_{1}(p); y_{2}(p)&amp;lt;/math&amp;gt; обладают свойством периодичности, с периодом &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt;. Покажем это для &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt;y_{1}(p+\frac{n}{2})=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{k(p+\frac{n}{2})}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}\omega_{\frac{n}{2}}^{\frac{kn}{2}}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как: &amp;lt;math&amp;gt;\omega_{\frac{n}{2}}^{\frac{kn}{2}}=e^{\frac{-4kni\pi}{2n}}=e^{-2ki\pi}=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Далее необходимо определить &amp;lt;math&amp;gt;y(p)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;p\geq \frac{n}{2}&amp;lt;/math&amp;gt;. Теперь воспользуемся свойством периодичности обнаруженными нами ранее: &amp;lt;math&amp;gt;y(p+\frac{n}{2})=y_{1}(p+\frac{n}{2})+\omega_{n}^{(p+\frac{n}{2})}y_{2}(p+\frac{n}{2})&amp;lt;/math&amp;gt;. Воспользуемся вторым свойством омеги: &amp;lt;math&amp;gt;y(p)=y_{1}(p)-\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&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;
Бит-реверсирование – это преобразование двоичного числа путем изменения порядка следования бит в нем на противоположный. Бит-реверсирование применимо только к индексам элементов массива и предназначено для изменения порядка следования этих элементов, при этом значения самих элементов не изменяются. Так, к примеру, четырехмерный вектор &amp;lt;math&amp;gt;(x_{0},x_{1},x_{2},x_{3})&amp;lt;/math&amp;gt; изменится на &amp;lt;math&amp;gt;(x_{0},x_{2},x_{1},x_{3})&amp;lt;/math&amp;gt;. Данная процедура необходима для разделения исходного сигнала на элементы подмножеств с четными и нечетными номерами, которые необходимы для вычисления &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Вычисление БПФ&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
После выполнения этапа бит-реверсирования выполняются вычисления проиллюстрированные &#039;&#039;на рисунке 2&#039;&#039; в разделе [[#Информационный граф|Информационный граф]]. Рисунок 2 приведен для случая четырехмерного входного сигнала. Он предполагает совершение двух этапов. В общем же количество этапов равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Каждый этап содержит в себе вычисление одной и той же операции &amp;quot;бабочки&amp;quot;, где на вход подаются два числа &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;, на выходе же получаются &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;- это &#039;&#039;коэффициент поворота&#039;&#039;. Общая формула для коэффициента поворота: &amp;lt;math&amp;gt;e^{\frac{-ji\pi}{k}}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;- размер шага &amp;quot;бабочки&amp;quot;, &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;- номер итерации вычислений над группой элементов, размером &amp;lt;math&amp;gt;2k&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;(0\leq j &amp;lt; k )&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из двух вышеописанных этапов БПФ, бит-реверсирование является самым быстровыполнимым. Поэтому вычислительное ядро алгоритма будет лежать именно во втором этапе. А именно, им будет являться вычисление &amp;quot;бабочек&amp;quot;. Количество этапов выполнения БПФ, как описывалось выше, равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Учитывая количество этапов, размерность входного сигнала и идею деления на подмножества, всего &amp;quot;бабочек&amp;quot; получается &amp;lt;math&amp;gt;\frac{nlog_{2}n}{2}&amp;lt;/math&amp;gt;. При этом операция умножения выполняется &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; раз. Это связано с тем, что результаты вычисления &amp;quot;бабочки&amp;quot; &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из всего вышесказанного заключаем, что макроструктуру алгоритма легче всего описать при помощи рекурсии: &lt;br /&gt;
*&amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; преобразований Фурье порядка 2&lt;br /&gt;
*умножение &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; пар комплексных чисел&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&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;
*выполнение операции &amp;quot;бабочка&amp;quot;&lt;br /&gt;
*выполнение БПФ&lt;br /&gt;
*функция вывода результата (для тестирования)&lt;br /&gt;
----&lt;br /&gt;
&#039;&#039;&#039;главный программный код БПФ:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; *inputSignal = NULL;   &lt;br /&gt;
	complex&amp;lt;double&amp;gt; *outputSignal = NULL; &lt;br /&gt;
	int size = 0;&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;FFT. Serial version.&amp;quot; &amp;lt;&amp;lt; endl;       &lt;br /&gt;
	ProcessInitialization(inputSignal, outputSignal, size);&lt;br /&gt;
	bit(inputSignal, outputSignal, size);&lt;br /&gt;
	SerialFFTCalculation(outputSignal, size);&lt;br /&gt;
	PrintSignal(outputSignal, size); //в случае тестирования&lt;br /&gt;
	ProcessTermination(inputSignal, outputSignal);&lt;br /&gt;
	cin.get();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
=== функция бит-реверсирования ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void bit(complex&amp;lt;double&amp;gt; inputSignal[], complex&amp;lt;double&amp;gt; *outputSignal, int size)&lt;br /&gt;
{ // процесс бит-реверсирования&lt;br /&gt;
	int j = 0, i = 0;&lt;br /&gt;
	while (i &amp;lt; size) &lt;br /&gt;
	{&lt;br /&gt;
		if (j &amp;gt; i) &lt;br /&gt;
		{ &lt;br /&gt;
			outputSignal[i] = inputSignal[j];       &lt;br /&gt;
			outputSignal[j] = inputSignal[i]; &lt;br /&gt;
		}&lt;br /&gt;
		else       &lt;br /&gt;
			if (j == i)          &lt;br /&gt;
				outputSignal[i] = inputSignal[i];&lt;br /&gt;
		&lt;br /&gt;
		int m = size &amp;gt;&amp;gt; 1;     &lt;br /&gt;
		while ((m &amp;gt;= 1) &amp;amp;&amp;amp; (j &amp;gt;= m)) &lt;br /&gt;
		{ &lt;br /&gt;
			j -= m;       &lt;br /&gt;
			m = m &amp;gt;&amp;gt; 1; &lt;br /&gt;
		}     &lt;br /&gt;
		j += m;     &lt;br /&gt;
		i++;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация размерности входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessInitialization(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal, int &amp;amp;size) &lt;br /&gt;
{&lt;br /&gt;
	// инициализация размера входного сигнала  &lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		cout &amp;lt;&amp;lt; &amp;quot;Enter the input signal length: &amp;quot;;&lt;br /&gt;
		cin &amp;gt;&amp;gt; size;&lt;br /&gt;
		if (size &amp;lt; 4)&lt;br /&gt;
			cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be &amp;gt;= 4&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
		else&lt;br /&gt;
		{&lt;br /&gt;
			int tmpSize = size;&lt;br /&gt;
			while (tmpSize != 1)&lt;br /&gt;
			{&lt;br /&gt;
				if (tmpSize % 2 != 0)&lt;br /&gt;
				{&lt;br /&gt;
					cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be powers of two&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
					size = -1;&lt;br /&gt;
					break;&lt;br /&gt;
				}&lt;br /&gt;
				tmpSize /= 2;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	while (size &amp;lt; 4);  &lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Input signal length = &amp;quot; &amp;lt;&amp;lt; size &amp;lt;&amp;lt; endl; &lt;br /&gt;
	inputSignal = new complex&amp;lt;double&amp;gt;[size];   &lt;br /&gt;
	outputSignal = new complex&amp;lt;double&amp;gt;[size];&lt;br /&gt;
	DummyDataInitialization(inputSignal, size);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void DummyDataInitialization(complex&amp;lt;double&amp;gt;* mas, int size)&lt;br /&gt;
{	&lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)&lt;br /&gt;
	{&lt;br /&gt;
		mas[i] = int(rand() % 100);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== процесс очистки памяти ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessTermination(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal) &lt;br /&gt;
{ &lt;br /&gt;
	//очистка памяти&lt;br /&gt;
	delete[] inputSignal;   &lt;br /&gt;
	inputSignal = NULL;   &lt;br /&gt;
	delete[] outputSignal;   &lt;br /&gt;
	outputSignal = NULL; &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение операции &amp;quot;бабочка&amp;quot; ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void Butterfly(complex&amp;lt;double&amp;gt; *signal, complex&amp;lt;double&amp;gt; &amp;amp;u,int offset, int butterflySize)&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; tem = signal[offset + butterflySize] * u;&lt;br /&gt;
	signal[offset + butterflySize] = signal[offset] - tem;   &lt;br /&gt;
	signal[offset] += tem;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение БПФ ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void SerialFFTCalculation(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	int m = 0;   &lt;br /&gt;
	for (int tmp_size = size; tmp_size &amp;gt; 1; tmp_size /= 2, m++);&lt;br /&gt;
	for (int p = 0; p &amp;lt; m; p++)&lt;br /&gt;
	{&lt;br /&gt;
		int butterflyOffset = 1 &amp;lt;&amp;lt; (p + 1);&lt;br /&gt;
		int butterflySize = butterflyOffset &amp;gt;&amp;gt; 1;&lt;br /&gt;
		double coeff = M_PI / butterflySize;&lt;br /&gt;
&lt;br /&gt;
		for (int i = 0; i &amp;lt; size / butterflyOffset; i++)&lt;br /&gt;
			for (int j = 0; j &amp;lt; butterflySize; j++)&lt;br /&gt;
				Butterfly(signal,complex&amp;lt;double&amp;gt;(cos(-j*coeff),sin(-j*coeff)),j+i*butterflyOffset,butterflySize);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== функция вывода результата (для тестирования) ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void PrintSignal(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Result signal&amp;quot; &amp;lt;&amp;lt; endl;   &lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)     &lt;br /&gt;
		cout &amp;lt;&amp;lt; signal[i] &amp;lt;&amp;lt; endl;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотренный алгоритм  Cooley – Tukey выполняется за &amp;lt;math&amp;gt;nlog_{2}n&amp;lt;/math&amp;gt; операций комплексного сложения и &amp;lt;math&amp;gt;\frac{n}{2}log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения. Таким образом последовательная сложность алгоритма является &#039;&#039;&#039;линейно-логарифмической&#039;&#039;&#039;.&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[file:Граф.jpg|center|thumb|800px|Рисунок 1. Алгоритм Cooley – Tukey для n=4, при предварительно сделанном бит-реверсировании]]&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
[[file:Парал.jpg|center|thumb|800px|Рисунок 2. Распараллеливание алгоритма Cooley – Tukey для n=8]]&lt;br /&gt;
Рисунок 2 получен из информационного графа, главной идеей которого является процедура деления множеств полученных на предыдущем шаге на два равных по мощности подмножества и применение к ним БПФ. То есть процесс вычисления распределяется между &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; процессорами. И так как при последовательной реализации алгоритма было &amp;lt;math&amp;gt;\frac{n}{2}\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;n\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. Получается, что при параллельной реализации алгоритма мы будем иметь &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;2\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. При этом, если используется функция HT(Hyper threading), то параллельная сложность быстрого преобразования Фурье (базовый алгоритм Кули-Тьюки) для векторов с длиной, равной степени двойки составляет &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt;.&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
*&#039;&#039;&#039;Вещественное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{R}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
*&#039;&#039;&#039;Комплексное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
*Соотношение последовательной и параллельной сложности является &#039;&#039;линейным&#039;&#039;. Это хорошо видно из пункта [[#Ресурс параллелизма алгоритма|ресурс параллелизма алгоритма]].&lt;br /&gt;
*Вычислительная мощность алгоритма является &#039;&#039;логарифмической&#039;&#039;.&lt;br /&gt;
*Устойчивость. &lt;br /&gt;
**Рассмотрим вопрос об устойчивости поближе. Так как БПФ отличается от ДПФ простым лишь сокращением одинаковых операций, то вопрос устойчивости можно рассматривать на примере ДПФ. Так, к примеру, возьмем формулу ДПФ и добавим возмущение: &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}(x_{k}+\varepsilon )e^{\frac{-2*\pi*k*p*i}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. После чего преобразуем ее: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2*\pi*k*p*i}{n}}+\varepsilon\sum_{k=0}^{n-1} e^{\frac{-2*\pi*k*p*i}{n}}&amp;lt;/math&amp;gt; и оценим вторую сумму, которая является геометрической прогрессией со знаменателем &amp;lt;math&amp;gt;q=e^{\frac{-2*\pi*p*i}{n}}&amp;lt;/math&amp;gt;. Таким образом оцениваемая сумма будет равняться: &amp;lt;math&amp;gt;\varepsilon\frac{1-e^{\frac{-2*\pi*p*i*(n-1))}{n}}}{1-e^{\frac{-2*\pi*p*i}{n}}}&amp;lt;/math&amp;gt;. При достаточно больших размерностях входного сигнала данное выражение сводится к: &amp;lt;math&amp;gt;\varepsilon (1-e^{-2*\pi*p*i})&amp;lt;/math&amp;gt;. В тригонометрической форме данного выражения: &amp;lt;math&amp;gt;\varepsilon (cos(\frac{2*\pi*p*(n-1)}{n})-i*sin(\frac{2*\pi*p*(n-1)}{n}))&amp;lt;/math&amp;gt; видно, что принимая во внимание целочисленность индексов &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; получается 0. Таким образом, мы можем говорить об устойчивости алгоритма в целом, и абсолютной устойчивости алгоритма в асимптотике.&lt;br /&gt;
*исходя из информационного графа, можно сделать вывод о сбалансированности алгоритма.&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:масш.jpg|center|thumb|1000px|Рисунок 3. Параллельная реализация алгоритма. Изменение эффективности(%)в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
Как описывалось выше, каждый процессор получает достаточно низкий объем работы, что приводит к неэффективному распараллеливанию алгоритма (высокие затраты на передачу данных между процессорами по сравнению с вычислениями на них). Поэтому необходимо находить оптимальное количество процессоров для каждой отдельно взятой задачи. В данном примере, при размерности входного сигнала менее 262144, оптимальное количество процессоров составляет 1. При размерности 262144-4194304 оптимум достигался при 8 процессорах. И при размерности более 4194304- 16 процессоров. Вычисления проводились на суперкомпьютере «Ломоносов». &lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Самой распространенной и известной реализацией дискретного БПФ является библиотека [http://www.fftw.org/ FFTW], разработанная Маттео Фриго и Стивеном Г. Джонсоном в Массачусетском технологическом институте. Данная библиотека существует для языков C и FORTRAN и позволяет производить вычисления в одном или нескольких измерениях. Входной сигнал может быть как действительным так и комплексным, а его размерность не обязательно должна быть степенью двойки. На данный момент последняя версия FFTW 3.3.5. Другие особенности библиотеки:&lt;br /&gt;
*Скорость. (Поддержка SSE / SSE2 / Altivec, начиная с версии 3.0. Версия 3.3.1 поддерживает AVX и ARM Neon.)&lt;br /&gt;
*Параллельные вычисления (OpenMP, MPI).&lt;br /&gt;
*[http://www.fftw.org/fftw3_doc/ Документация] в форматах HTML, PDF.&lt;br /&gt;
*Данное программное обеспечение является свободным и распространяется по лицензии GNU (General Public License).&lt;br /&gt;
Здесь приводится пример реализации прямого БПФ для комплексных данных входного сигнала на языке C++. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;fftw3-mpi.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/stat.h&amp;gt;&lt;br /&gt;
#include &amp;lt;fcntl.h&amp;gt;&lt;br /&gt;
# include &amp;lt;time.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
	const ptrdiff_t N0 = 4194304;&lt;br /&gt;
	fftw_plan planForw;&lt;br /&gt;
	fftw_complex *data, *dataOut;&lt;br /&gt;
	ptrdiff_t alloc_local, local_ni, local_i_start, i, j, local_no, local_o_start;&lt;br /&gt;
	int index, size;&lt;br /&gt;
	double startwtime, endwtime;&lt;br /&gt;
	MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
	fftw_mpi_init();&lt;br /&gt;
	MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;index);&lt;br /&gt;
	MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);&lt;br /&gt;
&lt;br /&gt;
	/* get local data size and allocate */&lt;br /&gt;
	alloc_local = fftw_mpi_local_size_1d(N0, MPI_COMM_WORLD, FFTW_FORWARD, FFTW_ESTIMATE,&lt;br /&gt;
		&amp;amp;local_ni, &amp;amp;local_i_start, &amp;amp;local_no, &amp;amp;local_o_start);&lt;br /&gt;
	data = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	dataOut = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	/* create plan  */&lt;br /&gt;
	planForw = fftw_mpi_plan_dft_1d(N0, data, dataOut, MPI_COMM_WORLD,&lt;br /&gt;
		FFTW_FORWARD, FFTW_ESTIMATE);&lt;br /&gt;
	/* initialize data to some function my_function(x,y) */&lt;br /&gt;
	for (i = 0; i &amp;lt; local_ni; ++i)&lt;br /&gt;
	{&lt;br /&gt;
		data[i][0] = rand() / (double)RAND_MAX;&lt;br /&gt;
		data[i][1] = rand() / (double)RAND_MAX;&lt;br /&gt;
	}&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		startwtime = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_execute(planForw);&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		endwtime = MPI_Wtime();&lt;br /&gt;
		printf(&amp;quot;wall clock time = %f\n&amp;quot;,&lt;br /&gt;
			endwtime - startwtime);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_destroy_plan(planForw);&lt;br /&gt;
	MPI_Finalize();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
[http://www.fftw.org/download.html Скачав] библиотеку и установив ее на рабочий компьютер, можно поэкспериментировать, используя вышеописанный исходный код. При этом, на стадии компиляции необходимо указывать директорию, где находятся заголовочные файлы и директорию, где находятся библиотечные файлы. Пример: mpicxx name.cpp -I/path/to/include/files -L/path/to/lib/files  -lfftw3_mpi -lfftw3 -lm. Для версии 2.1.4 описание на русском языке приводится [https://parallel.ru/cluster/fftw.html здесь].&lt;br /&gt;
&lt;br /&gt;
Остальные реализации:&lt;br /&gt;
*[http://www.gnu.org/software/gsl/ GSL - GNU Scientific Library]&lt;br /&gt;
*[https://developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
*[http://www-03.ibm.com/systems/power/software/essl/ Engineering and Scientific Subroutine Library (ESSL) and Parallel ESSL]&lt;br /&gt;
=Литература=&lt;br /&gt;
[1] Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний &lt;br /&gt;
&lt;br /&gt;
[2] Г. Нуссбаумер. Быстрое преобразование Фурье и алгоритмы вычисления сверток &lt;br /&gt;
&lt;br /&gt;
[3] А.О. Лапаев Параллельное вычисление дискретного преобразования Фурье полинома в простом поле&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Victoria_Stepanischeva&amp;diff=17575</id>
		<title>Обсуждение участника:Victoria Stepanischeva</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Victoria_Stepanischeva&amp;diff=17575"/>
		<updated>2016-11-16T10:46:47Z</updated>

		<summary type="html">&lt;p&gt;IgorS: /* Замечания к статье */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Замечания к статье ===&lt;br /&gt;
&lt;br /&gt;
* содержание не соответствуют шаблону описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:21, 25 октября 2016 (MSK)&lt;br /&gt;
* не указан конкретный вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:21, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 1.9 не раскрыт--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:21, 25 октября 2016 (MSK)&lt;br /&gt;
* п.п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:46, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* Раздел про общее описание алгоритма слишком большой и нарезан на много частей. Было бы лучше написать статью про саму задачу преобразования Фурье, где и упоминались бы в частности области применения, а также разные алгоритмы с краткими описаниями (а впоследствии со ссылками на статьи с полным описанием) [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:56, 11 ноября 2016 (MSK)&lt;br /&gt;
* При упоминании рекурсивного решения задачи следует сказать о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:56, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Victoria_Stepanischeva/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(FFT)&amp;diff=17574</id>
		<title>Участник:Victoria Stepanischeva/Быстрое преобразование Фурье (FFT)</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:Victoria_Stepanischeva/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(FFT)&amp;diff=17574"/>
		<updated>2016-11-16T10:46:12Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Авторы:&lt;br /&gt;
&lt;br /&gt;
* [[Участник:Victoria_Stepanischeva|Степанищева В.С.]], группа 601 (Общее описание алгоритма, Вычислителное ядро алгоритма, Макроструктура алгоритма, Информационный граф, Ресурс параллелизма алгоритма)&lt;br /&gt;
* [[Участник:Igor_Munkin|Мунькин И.А.]], группа 601 (Математическое описание алгоритма, Схема реализации последовательного алгоритма, Последовательная сложность алгоритма, Входные и выходные данные алгоритма, Свойства алгоритма, Существующие реализации алгоритма)&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразования Фурье&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \; log \, N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(log \,N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&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;
[https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss Карлом Фридрихом Гауссом]&lt;br /&gt;
еще в 1805 году для интерполяции траекторий астероидов, однако эта работа не&lt;br /&gt;
получила широкого признания. Алгоритм быстрого преобразования Фурье обрел свою&lt;br /&gt;
популярность благодаря работе&lt;br /&gt;
[https://en.wikipedia.org/wiki/James_Cooley James Cooley], работающего в то&lt;br /&gt;
время в [https://en.wikipedia.org/wiki/IBM IBM], и&lt;br /&gt;
[https://en.wikipedia.org/wiki/John_Tukey John Tukey], параллельно принимающего&lt;br /&gt;
участие в разработках [https://en.wikipedia.org/wiki/Bell_Labs Bell Labs] и&lt;br /&gt;
работающего в&lt;br /&gt;
[https://en.wikipedia.org/wiki/Princeton_University Принстонском университетe].&lt;br /&gt;
В их статье, изданной в 1965 году, они представили подробное описание алгоритма&lt;br /&gt;
и его программной реализации.&lt;br /&gt;
&lt;br /&gt;
====Разновидности алгоритма быстрого преобразования Фурье====&lt;br /&gt;
Помимо традиционного и наиболее распространенного алгоритма&lt;br /&gt;
[https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Cooley-Tukey]&lt;br /&gt;
существует также несколько других часто используемых реализаций алгоритма&lt;br /&gt;
быстрого преобразования Фурье.&lt;br /&gt;
&lt;br /&gt;
=====PFA-алгоритм=====&lt;br /&gt;
Алгоритм [https://en.wikipedia.org/wiki/Prime-factor_FFT_algorithm prime-factor]&lt;br /&gt;
(PFA), также называемый алгоритмом Good-Thomas, представляет собой алгоритм&lt;br /&gt;
быстрого преобразования Фурье, который представляет дискретное преобразование&lt;br /&gt;
Фурье размера &amp;lt;math&amp;gt;N = N_1 \cdot N_2&amp;lt;/math&amp;gt; в виде двумерного&lt;br /&gt;
&amp;lt;math&amp;gt;N_1 \times N_2&amp;lt;/math&amp;gt; дискретного преобразования Фурье, при условии, что&lt;br /&gt;
&amp;lt;math&amp;gt;N_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;N_2&amp;lt;/math&amp;gt; - взаимно простые числа. Представленные&lt;br /&gt;
преобразования меньшего размера могут быть вычислены либо рекурсивным&lt;br /&gt;
применением PFA либо используя какой-либо иной метод быстрого преобразования&lt;br /&gt;
Фурье.&lt;br /&gt;
&lt;br /&gt;
=====Алгоритм Bluestein=====&lt;br /&gt;
Алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Bluestein],&lt;br /&gt;
обычно называемый &amp;lt;math&amp;gt;\mathcal{Z}&amp;lt;/math&amp;gt;-чирп алгоритмом, представляет собой&lt;br /&gt;
алгоритм быстрого преобразования Фурье, который вычисляет дискретное&lt;br /&gt;
преобразование Фурье различных размеров с помощью представления оного в виде&lt;br /&gt;
линейной свертки. Другой алгоритм быстрого преобразования Фурье, использующий&lt;br /&gt;
свойства простых чисел, алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Rader], также вычисляет&lt;br /&gt;
дискретное преобразование Фурье в виде свертки.&lt;br /&gt;
&lt;br /&gt;
====Приложения быстрого преобразования Фурье====&lt;br /&gt;
&lt;br /&gt;
Алгоритм быстрого преобразования Фурье имеет множество областей приложения,&lt;br /&gt;
таких как:&lt;br /&gt;
* Обработка изображений&lt;br /&gt;
* Цифровое сжатие&lt;br /&gt;
* Численное интегрирование&lt;br /&gt;
* Быстрые методы решения уравнения Пуассона&lt;br /&gt;
* Спектральные методы решения обыкновенных дифференциальных уравнений&lt;br /&gt;
&lt;br /&gt;
Рассмотрим подробнее те приложения, с которыми мы наиболее часто сталкиваемся&lt;br /&gt;
в повседневной жизни.&lt;br /&gt;
&lt;br /&gt;
=====Обработка изображений=====&lt;br /&gt;
Все фильтры делят на две группы: пространственные и частотные. Типов&lt;br /&gt;
пространственных фильтров достаточно много: линейные, медианные, изотропные и&lt;br /&gt;
др. Процесс фильтрации в этих фильтрах основан на простом применении маски&lt;br /&gt;
фильтра ко всем точкам изображения. В каждой точке отклик фильтра вычисляется с&lt;br /&gt;
использованием предварительно заданных связей. Все пространственные фильтры&lt;br /&gt;
имеют линейную трудоемкость.&lt;br /&gt;
&lt;br /&gt;
Частотная фильтрация является более общим механизмом фильтрации, чем&lt;br /&gt;
пространственная. Все пространственные фильтры можно реализовать с помощью&lt;br /&gt;
частотных. Частотные фильтры выполняют фильтрацию изображений в частотной&lt;br /&gt;
области. Для этого с помощью преобразования Фурье происходит получение спектра&lt;br /&gt;
изображения. После применения фильтра к спектру изображения выполняется обратное&lt;br /&gt;
преобразование Фурье и получается отфильтрованное изображение.&lt;br /&gt;
&lt;br /&gt;
Простота фильтрации в частотной области заключается в простой интерпретации&lt;br /&gt;
частот:&lt;br /&gt;
* низкие частоты - плавное изменение яркости изображения&lt;br /&gt;
* высокие частоты - быстрое изменение яркости изображения (границы объектов)&lt;br /&gt;
За счет изменения значений определенных частот можно добиться размытия&lt;br /&gt;
изображения, повышение резкости и др.&lt;br /&gt;
&lt;br /&gt;
=====Цифровое сжатие=====&lt;br /&gt;
Одним из первых форматов файла-контейнера для хранения записи оцифрованного&lt;br /&gt;
аудипотока является формат WAV. Этот формат чаще всего используется в качестве&lt;br /&gt;
оболочки для несжатого звука. При таком кодировании в WAV-файле хранится полная&lt;br /&gt;
информация об исходном звуке, оцифрованном и проквантованном с некоторой&lt;br /&gt;
частотой, например 44 кГц. Этой информации абсолютно достаточно для&lt;br /&gt;
воспроизведения всех частот исходного сигнала, меньших половины частоты&lt;br /&gt;
квантования (по [https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%9A%D0%BE%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B8%D0%BA%D0%BE%D0%B2%D0%B0 теореме Котельникова] ). При этом одна&lt;br /&gt;
минута записи занимает около 15 Мб, поэтому в настоящее время очень популярным&lt;br /&gt;
стал формат кодирования MPEG Layer-3 со сжатием.&lt;br /&gt;
&lt;br /&gt;
При сжатии кодеком MPEG Layer-3 исходный звуковой сигнал разбивается на&lt;br /&gt;
фрагменты, длительностью по 50 миллисекунд, каждый из которых анализируется&lt;br /&gt;
отдельно. При анализе фрагмент раскладывается на гармоники по методу Фурье, из&lt;br /&gt;
которых в соответствии с теорией восприятия звука человеческим ухом&lt;br /&gt;
выбрасываются те гармоники, которые человек хуже воспринимает на фоне остальных:&lt;br /&gt;
* более тихие гармоники на фоне более громких&lt;br /&gt;
* звуки, замаскированные вследствие инертности слуха (так, например, если за очень громким хлопком, с задержкой в долю секунды, пойдет какой-то иной кратковременный сигнал, то его слышно не будет)&lt;br /&gt;
&lt;br /&gt;
При воспроизведении файла закодированного MPEG Layer-3 производится обратное&lt;br /&gt;
преобразование, при котором оставшиеся гармоники вновь преобразуются в звуковую&lt;br /&gt;
волну. Поскольку часть информации об исходном сигнале была удалена, звук&lt;br /&gt;
получается не совпадающий с исходным. Одной из основных характеристик качества&lt;br /&gt;
аудио потока является битрейт.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Битрейт&#039;&#039; - это объем информации на каждую секунду записи:&lt;br /&gt;
* чем он меньше, тем меньший размер имеют файлы с одинаковой по времени длине&lt;br /&gt;
* чем он меньше, тем большее количество &amp;quot;лишних&amp;quot; гармоник приходится удалять&lt;br /&gt;
&lt;br /&gt;
===Математическое описание алгоритма===&lt;br /&gt;
Пусть задан вектор действительных чисел&lt;br /&gt;
&amp;lt;math&amp;gt;\left(f_1, f_2, f_3, \dots, f_{N-1} \right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Дискретное преобразование Фурье определено следующим образом&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_k = \sum_{n = 0}^{N - 1}\, f_n\, e^{\left(\frac{i2\pi kn}{N}\right)}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обозначим &amp;lt;math&amp;gt;W_N = e^{\left(\frac{i2\pi}{N}\right)}&amp;lt;/math&amp;gt; - &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;-ый&lt;br /&gt;
корень единицы. Таким образом &amp;lt;math&amp;gt;(W_N)^N = 1&amp;lt;/math&amp;gt;. Несложные математические&lt;br /&gt;
преобразования приводят формулу дискретного преобразования Фурье к следующему&lt;br /&gt;
виду&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_k = \sum_{n = 0}^{N - 1}\, f_n\, W_n^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, алгоритм вычисления дискретного преобразования Фурье состоит из&lt;br /&gt;
2 шагов:&lt;br /&gt;
# Вычисление поворотных коэффициентов &amp;lt;math&amp;gt;W_n^{-nk},\; n,k = 0, 1, \dots, N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Вычисление коэффициентов Фурье &amp;lt;math&amp;gt;F_k,\; k = 0, 1, \dots, N-1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В основе концепции быстрого преобразования Фурье лежит идея отдельного расчета четных и&lt;br /&gt;
нечетных коэффициентов с использованием векторов меньшего размера. Разобъем дискретное&lt;br /&gt;
преобразование Фурье следующим образом&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_k = \sum_{n = 0}^{N - 1}\, f_n\, W_{N}^{-nk}&lt;br /&gt;
        = \sum_{n = 0}^{N/2 - 1}\, g_n\, W_{N/2}^{-nk}&lt;br /&gt;
        + \sum_{n = 0}^{N/2 - 1}\, h_n\, W_{N/2}^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следовательно, четные и нечетные коэффициенты Фурье могут быть записаны в&lt;br /&gt;
следующем виде:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_{2k} = \sum_{n = 0}^{N/2 - 1}\, \underbrace{&lt;br /&gt;
        \left(f_n + f_{n + N/2}\right)&lt;br /&gt;
}_{g_n} \, W_{N/2}^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
F_{2k+1} = \sum_{n = 0}^{N/2 - 1}\, \underbrace{&lt;br /&gt;
        \left((f_n + f_{n + N/2}) W^{-n}\right)&lt;br /&gt;
}_{h_n} \, W_{N/2}^{-nk}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, вычисление дискретного преобразования Фурье от &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
коэффициентов сводится к вычислению 2 дискретных преобразований Фурье от&lt;br /&gt;
&amp;lt;math&amp;gt;N/2&amp;lt;/math&amp;gt; коэффициентов. Применяя данную идею рекурсивно, получаем&lt;br /&gt;
алгоритм быстрого преобразования Фурье.&lt;br /&gt;
&lt;br /&gt;
===Вычислительное ядро алгоритма===&lt;br /&gt;
&lt;br /&gt;
[[File:Stepmoon_buttefly.png|left|200px]]&lt;br /&gt;
В случае размера входных данных равному &amp;lt;math&amp;gt;N = 2^M&amp;lt;/math&amp;gt; вычислительным&lt;br /&gt;
ядром алгоритма являются так называемые &amp;quot;бабочки&amp;quot;. В элементарном случае&lt;br /&gt;
&amp;quot;бабочка&amp;quot; является двухточечным преобразованием.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
g_n = f_n + f_{n + N/2}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
h_n = \left(f_n - f_{n + N/2}\right)\, W^{-n}&lt;br /&gt;
&amp;lt;/math&amp;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/Divide_and_conquer_algorithms &amp;quot;разделяй и властвуй&amp;quot;].&lt;br /&gt;
&lt;br /&gt;
Рассмотрим общую структуру для входных данных размера &amp;lt;math&amp;gt;N = N1 \cdot N2&amp;lt;/math&amp;gt;&lt;br /&gt;
# Исходное множество точек разбивается на &amp;lt;math&amp;gt;N2&amp;lt;/math&amp;gt; равных подмножеств размером &amp;lt;math&amp;gt;N1&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Над каждым из полученных множеств выполняется быстрое преобразование Фурье. Для его вычисления над каждым из подмножеств рассматриваемого множества снова выполняется деление на равные части. Такое разбиение продолжается до тех пор, пока количество элементов, над которыми необходимо выполнить быстрое преобразование Фурье, не будет равно одному.&lt;br /&gt;
# После этого выполняется объединение всех промежуточных результатов. Полученные на предыдущем шаге множества объединяются за линейное время.&lt;br /&gt;
&lt;br /&gt;
Ниже представлена схема алгоритма Cooley-Tukey для &amp;lt;math&amp;gt;N = 2^M&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Stepmoon_macro.png|center|800px]]&lt;br /&gt;
&lt;br /&gt;
===Схема реализации последовательного алгоритма===&lt;br /&gt;
Ниже представлен прототип реализации для &amp;lt;math&amp;gt;N = 2^M&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;source lang=perl&amp;gt;&lt;br /&gt;
use Math::Complex;&lt;br /&gt;
&lt;br /&gt;
sub fft {&lt;br /&gt;
    my $input = shift;&lt;br /&gt;
    return $input if @{ $input } == 1;&lt;br /&gt;
    my @w = map {&lt;br /&gt;
        my $theta = 2 * pi * $_ / @{ $input };&lt;br /&gt;
        Math::Complex-&amp;gt;make(cos($theta), sin($theta));&lt;br /&gt;
    } (0 .. $#input);&lt;br /&gt;
    my $odd  = fft([ map { $input-&amp;gt;[2 * $_ + 1] }, (0 .. @{ $input } / 2 - 1) ]);&lt;br /&gt;
    my $even = fft([ map { $input-&amp;gt;[2 * $_] }, (0 .. @{ $input } / 2 - 1) ]);&lt;br /&gt;
    return [ map {&lt;br /&gt;
        $even-&amp;gt;[$_ % @{ $even }] + $w[$_] * $odd-&amp;gt;[$_ % @{ $odd }];&lt;br /&gt;
    } (0 .. $#input) ];&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Последовательная сложность алгоритма===&lt;br /&gt;
Если считать только главные члены выражений для последовательной сложности алгоритма, то простой алгоритм Cooley-Tukey может быть выполнен за &amp;lt;math&amp;gt;N\, log\, N&amp;lt;/math&amp;gt; операций. Таким образом, простой алгоритм Cooley-Tukey относится к линейно-логарифмическому классу по последовательной сложности.&lt;br /&gt;
&lt;br /&gt;
===Информационный граф===&lt;br /&gt;
Ниже представлен информационный граф для простого алгоритма Cooley-Tukey для &amp;lt;math&amp;gt;N = 8&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;\;h&amp;lt;/math&amp;gt; - операция сложения двух комплексных чисел, &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; - операция вычитания двух комплексных чисел и умножения результата вычитания на комплексное число (поворотный множитель). В последнем столбце операций умножения не производится.&lt;br /&gt;
&lt;br /&gt;
[[File:Stepmoon_Info_graph.png|center|500px]]&lt;br /&gt;
&lt;br /&gt;
===Ресурс параллелизма алгоритма===&lt;br /&gt;
Простой алгоритм Cooley-Tukey имеет критический путь, состоящий из&lt;br /&gt;
&amp;lt;math&amp;gt;log\, N&amp;lt;/math&amp;gt; операций сложения и вычитания и &amp;lt;math&amp;gt;log\, N&amp;lt;/math&amp;gt;&lt;br /&gt;
операций комплексного умножения (основание логарифма зависит от выбранного на&lt;br /&gt;
каждом шаге алгоритма разбиения). Следовательно, простой алгоритм быстрого&lt;br /&gt;
преобразования Фурье в общем случае может быть отнесен к логарифмическому классу&lt;br /&gt;
по параллельной сложности.&lt;br /&gt;
&lt;br /&gt;
===Входные и выходные данные алгоритма===&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; измеренные значения сигнала в дискретных временных точках с номерами &amp;lt;math&amp;gt;0 \dots N&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;f \in \mathbb{R}^N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных:&#039;&#039;&#039; &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; вектор компонент спектра исходного сигнала: &amp;lt;math&amp;gt;F \in \mathbb{C}^N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных:&#039;&#039;&#039; &amp;lt;math&amp;gt;N&amp;lt;/math&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;br /&gt;
===Динамические характеристики и эффективность реализации алгоритма===&lt;br /&gt;
&lt;br /&gt;
===Выводы для классов архитектур===&lt;br /&gt;
&lt;br /&gt;
===Существующие реализации алгоритма===&lt;br /&gt;
Наиболее популярной библиотекой, содержащей реализацию быстрого преобразования&lt;br /&gt;
Фурье, является [http://fftw.org/ FFTW], изначально разработанная сотрудниками&lt;br /&gt;
[https://en.wikipedia.org/wiki/Massachusetts_Institute_of_Technology MIT]&lt;br /&gt;
Matteo Frigo и Steven G. Johnson. На сегодняшний день исходный код библиотеки&lt;br /&gt;
распространяется под свободной лицензией&lt;br /&gt;
[https://en.wikipedia.org/wiki/GNU_General_Public_License#Version_2 GNU General Public Licence v2]&lt;br /&gt;
с помощью сервиса [https://github.com/FFTW/fftw3 Github], тем самым давая&lt;br /&gt;
возможность сообществу принимать участие в разработке. Несмотря на то, что сама&lt;br /&gt;
библиотека [http://fftw.org/faq/section2.html#whynotfortran написана на языке C]&lt;br /&gt;
существуют интерфейсы для работы с ней используя такие языки программирования&lt;br /&gt;
как Fortran, C++ и другиe.&lt;br /&gt;
&lt;br /&gt;
Для разбиения преобразований, размеры которых являются составным числом, на&lt;br /&gt;
меньшие используется одна из многочисленных реализаций алгоритма&lt;br /&gt;
[https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Cooley-Tukey]&lt;br /&gt;
(в зависимости от разложения на простые множители и паттернов доступа к памяти).&lt;br /&gt;
В свою очередь для преобразований, размеры которых являются простыми числом&lt;br /&gt;
используется либо алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Raider], либо алгоритм&lt;br /&gt;
[https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Bluestein].&lt;br /&gt;
Библиотека признана самой быстрой реализацией быстрого преобразования Фурье,&lt;br /&gt;
гарантируя на любых объемах входных данных сложность&lt;br /&gt;
&amp;lt;math&amp;gt;O \left(n\; log\, n \right)&amp;lt;/math&amp;gt;.  Существует реализация FFTW и с&lt;br /&gt;
поддержкой [https://en.wikipedia.org/wiki/Message_Passing_Interface MPI],&lt;br /&gt;
позволяющая работать с данными не помещающимися в память. Однако перегруппировка&lt;br /&gt;
данных приводит к оверхеду, которого трудно избежать при in-place вычислении&lt;br /&gt;
преобразований различных размеров.&lt;br /&gt;
&lt;br /&gt;
==Литература==&lt;br /&gt;
[1] L. Rabiner, B. Gold, &#039;&#039;&amp;quot;Theory and application of digital signal processing&amp;quot;&#039;&#039; // Prentice Hall, New Jersey, 1975.&lt;br /&gt;
&lt;br /&gt;
[2] Бахвалов Н.С., Жидков Н.П., Кобельков Г.М. &#039;&#039;&amp;quot;Численные методы&amp;quot;&#039;&#039; // БИНОМ. Лаборатория знаний, Москва, 2008.&lt;br /&gt;
&lt;br /&gt;
[3] M. Frigo, S. G. Johnson &#039;&#039;&amp;quot;FFTW Documentation&amp;quot;&#039;&#039; // [http://fftw.org/fftw3_doc/ fftw.org]&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=17573</id>
		<title>Обсуждение участника:Timokhinivan</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=17573"/>
		<updated>2016-11-16T10:45:25Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4-2.6 не соответствуют содержанию описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
* не указаны авторы статьи и их вклад--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
** Автор один, я (Тимохин Иван, 603 гр. ВМК МГУ) --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 14:50, 27 октября 2016 (MSK)&lt;br /&gt;
*** Так укажите себя, чтобы не было таких вопросов--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 31 октября 2016 (MSK).&lt;br /&gt;
* в п.п. 2.4 следует добавить исходный код программы и описание компьютерной платформы.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:45, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* В общем описании алгоритма нужны слова именно про алгоритм: следует рассказать идею быстрого преобразования, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не вполне понятны слова &amp;quot;первообразный корень из 1 степени N&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* В последней формуле раздела 1.2 индексы k и l, кажется, должны идти через запятую. Если там рассматривается их произведение, следует уточнить, какие значения принимает каждый из них [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2.3 - понятие &amp;quot;порядок преобразования&amp;quot; нуждается в пояснении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не хватает слов о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=17572</id>
		<title>Обсуждение участника:Timokhinivan</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Timokhinivan&amp;diff=17572"/>
		<updated>2016-11-16T10:45:15Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 2.4-2.6 не соответствуют содержанию описания алгоритма--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
* не указаны авторы статьи и их вклад--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 15:10, 25 октября 2016 (MSK)&lt;br /&gt;
** Автор один, я (Тимохин Иван, 603 гр. ВМК МГУ) --[[Участник:Timokhinivan|Timokhinivan]] ([[Обсуждение участника:Timokhinivan|обсуждение]]) 14:50, 27 октября 2016 (MSK)&lt;br /&gt;
*** Так укажите себя, чтобы не было таких вопросов--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:41, 31 октября 2016 (MSK).&lt;br /&gt;
* в п.п. 2.4 следует добавить исходный код программы и описание компьютерной платформы.&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* В общем описании алгоритма нужны слова именно про алгоритм: следует рассказать идею быстрого преобразования, с минимумом формул [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не вполне понятны слова &amp;quot;первообразный корень из 1 степени N&amp;quot; [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* В последней формуле раздела 1.2 индексы k и l, кажется, должны идти через запятую. Если там рассматривается их произведение, следует уточнить, какие значения принимает каждый из них [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Раздел 1.2.3 - понятие &amp;quot;порядок преобразования&amp;quot; нуждается в пояснении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;br /&gt;
* Не хватает слов о глубине рекурсии [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:46, 11 ноября 2016 (MSK)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17569</id>
		<title>Участник:Timokhinivan/Быстрое преобразование Фурье</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17569"/>
		<updated>2016-11-16T10:43:43Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразование Фурье&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Автор описания: [[U:Timokhinivan|Тимохин И.В.]], ВМК МГУ.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
В общем случае многомерное преобразование Фурье соответствует умножению на матрицу преобразования по каждой из координат, что для массива данных &amp;lt;math&amp;gt;N_1\times N_2 \times \cdots \times N_m&amp;lt;/math&amp;gt; означает выполнение &amp;lt;math&amp;gt;\sum_{i=1}^m \prod_{k\neq i} N_k&amp;lt;/math&amp;gt; одномерных преобразований. Ключевым наблюдением, приводящим к быстрому алгоримту для вычисления преобразования, является возможность свести (с некоторыми оговорками) одномерное преобразование к двумерному, которое для &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; требует &amp;lt;math&amp;gt;m + n&amp;lt;/math&amp;gt; рекурсивных вызовов, что, при применении такого метода на всех уровнях рекурсии, даёт общую сложность алгоритма &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Преобразование Фурье задаётся следующей формулой:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y_k = \sum_{l = 0}^{N-1} X_l \epsilon^{kl}_N,\qquad k = \overline{0,N-1}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;\epsilon_N = e^{-\frac{2i\pi}{N}}&amp;lt;/math&amp;gt; — первообразный корень&lt;br /&gt;
из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (т.е. &amp;lt;math&amp;gt;\epsilon_N&amp;lt;/math&amp;gt; является корнем из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, но не является корнем никакой меньшей степени). Число &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; при этом называется порядком преобразования.&lt;br /&gt;
&lt;br /&gt;
Таким образом, преобразование Фурье является линейным и задаётся&lt;br /&gt;
матрицей &amp;lt;math&amp;gt;F = \left\{ \epsilon^{kl}_N \right\}_{k,l = 0}^{N-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Многомерное преобразование Фурье ====&lt;br /&gt;
Естественным обобщением описанного одномерного преобразования Фурье является многомерный вариант:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y(l_1,\ldots,l_d) = \sum_{k_1 = 0}^{N_1-1} \cdots \sum_{k_d = 0}^{N_d-1} X(k_1,\ldots,k_d) \prod_{j=1}^{d} \epsilon_{N_j}^{l_j k_j}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Многомерное преобразование Фурье эквивалентно последовательному выполнению одномерных преобразований по каждому измерению; при использовании для этого рекурсивного алгоритма, общая сложность вычислений также составляет &amp;lt;math&amp;gt;O(N\log N)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;N = \prod_{j=1}^d N_j&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Сведение к (почти) двумерному преобразованию (алгоритм Кули-Тьюки) ====&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — составное, т.е. &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt;, то возможно&lt;br /&gt;
существенно сократить вычислительные расходы за счёт сведения к&lt;br /&gt;
двумерному преобразованию Фурье.  А именно, положим &amp;lt;math&amp;gt;X(l_1, l_2)&lt;br /&gt;
= X_{l_1 n + l_2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt; Y(k_1, k_2) = Y_{k_1 m + k_2}&amp;lt;/math&amp;gt;,&lt;br /&gt;
где &amp;lt;math&amp;gt;l_1, k_2 = \overline{0, m-1}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;l_2, k_1 =&lt;br /&gt;
\overline{0, n-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В этом случае&amp;lt;ref name=&amp;quot;bachv&amp;quot;&amp;gt;Н.С. Бахвалов, Н.П. Жидков, Г.М. Кобельков, &#039;&#039;Численные методы&#039;&#039;, 2003 г., ISBN: 5-94774-060-5&amp;lt;/ref&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{align}&lt;br /&gt;
    Y(k_1, k_2) &amp;amp;=&amp;amp; \sum_{l_2=0}^{n-1} (\epsilon^{k_2 l_2}_N \hat{X}(k_2, l_2)) \epsilon^{k_1 l_2}_n \\&lt;br /&gt;
    \hat{X}(k_2, l_2) &amp;amp;=&amp;amp; \sum_{l_1=0}^{m-1} X(l_1, l_2) \epsilon^{k_2 l_1}_m&lt;br /&gt;
  \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это даёт следующий алгоритм вычисления преобразования:&lt;br /&gt;
&lt;br /&gt;
# Записываем исходный вектор в матрицу &amp;lt;math&amp;gt;m\times n&amp;lt;/math&amp;gt; по строкам.&lt;br /&gt;
# Применяем к каждому столбцу преобразование Фурье.&lt;br /&gt;
# Умножаем элемент в позиции &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;\epsilon^{ij}_N&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Применяем к каждой строке преобразование Фурье.&lt;br /&gt;
# Результат записан в получившейся матрице по столбцам.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм уже даёт существенный выигрыш по сравнению с обычным&lt;br /&gt;
умножением матрицы на вектор: &amp;lt;math&amp;gt; O(m^2 n + n^2 m) &amp;lt;/math&amp;gt; против&lt;br /&gt;
&amp;lt;math&amp;gt;O(m^2 n^2)&amp;lt;/math&amp;gt;.  Однако наилучших результатов можно добиться,&lt;br /&gt;
если применять этот алгоритм рекурсивно на этапах 2 и 4.&lt;br /&gt;
&lt;br /&gt;
Так, если &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;, то целесообразно&lt;br /&gt;
на каждом уровне рекурсии «отщеплять» одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (уровней рекурсии при этом будет &amp;lt;math&amp;gt;K \sim \log N&amp;lt;/math&amp;gt;).  В этом&lt;br /&gt;
случае задача сводится к вычислению &amp;lt;math&amp;gt; \prod_{i\neq j} p_i &amp;lt;/math&amp;gt;&lt;br /&gt;
преобразований Фурье порядка &amp;lt;math&amp;gt;p_j&amp;lt;/math&amp;gt; для всех&lt;br /&gt;
&amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;.  При небольших &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (например, 2), это&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; Y_0 = \sum_{l=0}^{N-1} X_l&amp;lt;/math&amp;gt; вычисляется непосредственно.&lt;br /&gt;
# Любым способом вычисляется &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; — первообразный корень мультипликативной группы поля &amp;lt;math&amp;gt;\mathbb{Z}_{N}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# &amp;lt;math&amp;gt; Y_{g^{N-p} \bmod N} = X_0 + \sum_{q=0}^{N-2} X_{g^q \bmod N} \epsilon_N^{g^{q-p}\bmod N} &amp;lt;/math&amp;gt;.  &lt;br /&gt;
&lt;br /&gt;
Последнее выражение представляет собой свёртку и вычисляется с помощью всё того же преобразования Фурье.  В принципе, это можно проделывать непосредственно, поскольку порядок свёртки &amp;lt;math&amp;gt;N - 1&amp;lt;/math&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;
&amp;lt;math&amp;gt;O(mn (\log m + \log n))&amp;lt;/math&amp;gt; операций против&lt;br /&gt;
&amp;lt;math&amp;gt;O(mn)&amp;lt;/math&amp;gt; для умножения на поправочные коэффициенты (шаг 3).&lt;br /&gt;
&lt;br /&gt;
То же верно и для алгоритма в целом в случае &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;;&lt;br /&gt;
а именно, суммарно наибольшие вычислительные затраты связаны с&lt;br /&gt;
вычислением преобразований Фурье порядка &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;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;n&amp;lt;/math&amp;gt; рекурсивных вызовов алгоритма.&lt;br /&gt;
# Умножение всех элементов рабочего вектора на поправочные коэффициенты.&lt;br /&gt;
# Ещё &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рекурсивных вызовов.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Псевдокод (для метода Кули-Тьюки):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;fft(X, N)&lt;br /&gt;
begin&lt;br /&gt;
  (m, n) = factor(N)&lt;br /&gt;
&lt;br /&gt;
  if (n == 1) // Предполагается, что n &amp;lt;= m&lt;br /&gt;
    direct_fft(X, N) // Через умножение на матрицу&lt;br /&gt;
&lt;br /&gt;
  // 1&lt;br /&gt;
  transpose(X, m, n) //Транспонирует X как матрицу m x n, хранящуюся по строкам&lt;br /&gt;
   &lt;br /&gt;
  // 2&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    fft(X(i * m : (i + 1) * m - 1), m)&lt;br /&gt;
&lt;br /&gt;
  // 3&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    do j = 0, m - 1&lt;br /&gt;
      X(i * m + j) *= eps(i * j, N)&lt;br /&gt;
&lt;br /&gt;
  // 4&lt;br /&gt;
  transpose(X, n, m)&lt;br /&gt;
&lt;br /&gt;
  // 5&lt;br /&gt;
  do j = 0, m - 1&lt;br /&gt;
    fft(X(j * n : (j + 1) * n - 1), n)&lt;br /&gt;
&lt;br /&gt;
  // 6&lt;br /&gt;
  transpose(X, m, n)&lt;br /&gt;
end&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь &amp;lt;code&amp;gt;eps&amp;lt;/code&amp;gt; может как вычислять соответствующие коэффициенты, так и брать их из заранее заготовленной таблицы.  Последний способ требует увеличения затрат по памяти, но позволяет существенно сэкономить на вычислениях, а потому является предпочтительным.&lt;br /&gt;
&lt;br /&gt;
Кроме того, шаги 3 и 4 можно переставлять произвольным образом (с очевидными изменениями в шаге 3).&lt;br /&gt;
&lt;br /&gt;
Особо следует отметить, что если порядок элементов в выходном массиве не важен, шаг 6 можно пропустить.  &lt;br /&gt;
&lt;br /&gt;
Так, например, если преобразование Фурье используется для вычисления свёртки векторов, то конкретный порядок элементов в их образах Фурье совершенно не важен, лишь бы он был одинаков для обоих векторов.  В этом случае в прямом преобразовании можно опустить шаг 6, а в обратном — 1.&lt;br /&gt;
&lt;br /&gt;
Если необходимо учитывать случай большого простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (что может и не требоваться, в зависимости от задачи), то в первом условном операторе следует дополнительно проверить величину &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; и, если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; большое, переключиться на алгоритм Радера.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Непосредственно из описания алгоритма получаем, что если сложность&lt;br /&gt;
вычисления преобразования Фурье обозначить &amp;lt;math&amp;gt;f_N&amp;lt;/math&amp;gt;, то&lt;br /&gt;
&amp;lt;math&amp;gt;f_{mn} = n f_{m} + m f_{n} + mn&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N = \prod_{i=1}^K p_i&amp;lt;/math&amp;gt;, и на каждом шаге&lt;br /&gt;
«отщеплять» от него одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt;, а преобразование Фурье для&lt;br /&gt;
&amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; вычислять «в лоб», то общее количество операций&lt;br /&gt;
составит &amp;lt;math&amp;gt;O\left(N\sum_{i=1}^K p_i\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В частности, если &amp;lt;math&amp;gt;p_i \leq P&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;K \leq \log_P N&amp;lt;/math&amp;gt;&lt;br /&gt;
и для сложности получаем &amp;lt;math&amp;gt;O(NP\log_P N)&amp;lt;/math&amp;gt;.  Полагая&lt;br /&gt;
&amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; фиксированным, получаем заявленную сложность &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
[[Файл:FourierRecursiveGraph.svg|center|thumb|600px|&#039;&#039;&#039;Рисунок 1.&#039;&#039;&#039; Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 15&amp;lt;/math&amp;gt;. Исходные данные обозначены фиолетовым, результат — красным.  Преобразования Фурье для &amp;lt;math&amp;gt;m = 5&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n = 3&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты представлено оранжевыми узлами.]]&lt;br /&gt;
&lt;br /&gt;
Каждый уровень рекурсивного вызова состоит из трёх этапов: рекурсивный вызов по столбцам, умножение на поправочные коэффициенты и рекурсивного вызова по строкам. Таким образом для высоты ЯПФ имеем&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 h(mn) = 1 + h(m) + h(n)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для «элементарных» БПФ высота ЯПФ будет та же, что и для [[Умножение плотной матрицы на вектор|умножения матрицы на вектор]].&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учётом разложения &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;h(N) = O\left(\sum_{i=1}^K p_i\right) = O(P \log_P N) = O(\log N)&amp;lt;/math&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;
&amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n&amp;lt;/math&amp;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;N&amp;lt;/math&amp;gt;.  Однако в частных случаях, возможны более компактные представления для параметров и результатов.&lt;br /&gt;
&lt;br /&gt;
Для описания этих случаев нам понадобится матрица&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Q = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;   \\ &lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp; 1 \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp; 1 &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp; \cdots &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp; 1 &amp;amp;        &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp; 1 &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — вещественный, то &amp;lt;math&amp;gt;Y = Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — чисто мнимый, то &amp;lt;math&amp;gt;Y = -Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — вещественный.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = -Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — чисто мнимый.&lt;br /&gt;
&lt;br /&gt;
В каждом случае, &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; требуют для своего хранения в два раза меньше памяти, чем в общем случае.&lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии дополнительной информации о входном векторе, возможно сократить объём памяти используемый для хранения входных и выходных данных.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
Обозначая матрицу преобразования &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, имеем&amp;lt;ref name=&amp;quot;voevodtyrt&amp;quot;&amp;gt;В.В. Воеводин, Е.Е. Тыртышников, &#039;&#039;Вычислительные процессы с теплицевыми матрицами&#039;&#039;, Москва, «Наука», 1987 г.&amp;lt;/ref&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;F = F^T&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^{-1} = \tfrac{1}{N} F^*&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^* = QF &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Свойство 2 представляет особый интерес, поскольку позволяет вычислять обратное преобразование Фурье по тем же алгоритмам, что и прямое, поскольку &amp;lt;math&amp;gt;F^*&amp;lt;/math&amp;gt; представляет собой матрицу того же вида, что и &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, но с &amp;lt;math&amp;gt;\bar{\epsilon}&amp;lt;/math&amp;gt; вместо &amp;lt;math&amp;gt;\epsilon&amp;lt;/math&amp;gt;.  В сущности, свойство 3 позволяет свести задачу к исходной непосредственно, но ценой дополнительных пересылок.&lt;br /&gt;
&lt;br /&gt;
Кроме того, из второго свойства следует&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 \|F x\|_2 = \sqrt{N} \|x\|_2&lt;br /&gt;
&amp;lt;/math&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;\epsilon^{l}_{n} = \epsilon^{lm}_{mn} = \epsilon^{lm}_{N}&amp;lt;/math&amp;gt;, все необходимые рекурсивному вызову значения поворотных коэффициентов уже имеются в используемом в текущем вызове массиве, а потому его достаточно подготовить один раз на весь алгоритм.&lt;br /&gt;
&lt;br /&gt;
==== Нерекурсивный случай ====&lt;br /&gt;
&lt;br /&gt;
На нижнем уровне рекурсии преобразование Фурье вычисляется путём умножения на матрицу.  Поскольку этот алгоритм, вообще говоря, более пригоден для векторизации, чем рекурсивная реализация, имеет смысл не разбивать &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; на &#039;&#039;совсем&#039;&#039; мелкие множители (&amp;lt;math&amp;gt;p_i \equiv 2&amp;lt;/math&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;
&#039;&#039;Многие&#039;&#039; используемые в алгоритме данные зависят только от порядка преобразования — массив поворотных коэффициентов, факторизация &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; в алгоритме Радера, конкретная задаваемая им перестановка и оптимальный порядок свёртки там же.  Вычисление некоторых из них сопоставимо по сложности со всем остальным алгоритмом.  Имея это ввиду, если планируется вычисление нескольких преобразований Фурье одного порядка, имеет смысл подготовить все эти данные заранее.&lt;br /&gt;
&lt;br /&gt;
==== Численная устойчивость ====&lt;br /&gt;
&lt;br /&gt;
Поскольку все преобразования в алгоритме являются, с точностью до константы &amp;lt;math&amp;gt;\leq \sqrt{N}&amp;lt;/math&amp;gt;, унитарными, катастрофического роста ошибок округления не предвидится, а потому все вычисления возможно проводить с целевой точностью.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
[[Файл:IT_FFTMemaccess.png|center|thumb|800px|Рисунок 2. Профиль обращений в память простого алгоритма Кули-Тьюки с &amp;lt;math&amp;gt;p = (2, 2, 3, 5, 2)&amp;lt;/math&amp;gt;. Сверху вниз: вспомогательная память для прямого умножения матрицы на вектор, память для хранения поворотных коэффициентов, рабочий вектор. Реализация не использует транспонирования векторов в явном виде.]]&lt;br /&gt;
&lt;br /&gt;
Как видно, алгоритм в целом обладает невысокой локальностью, что связано с его структурой (множество рекурсивных шагов, каждый из которых охватывает существенную часть рабочего вектора) и организацией доступа к поворотным коэффициентам. Некоторого улучшения локальности можно добиться, если транспонировать рабочую «матрицу» в памяти, но это либо требует дополнительной памяти, либо, если транспонировать «на месте», существенно усложняет алгоритм.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В случае составного &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; с &#039;&#039;достаточно&#039;&#039; большими делителями, наиболее очевидным и простым вариантом является распределение по процессорам рекурсивных вызовов алгоритма Кули-Тьюки. В этом случае на весь алгоритм приходится только три пересылки, но зато они проходят по схеме «все-со-всеми» (транспонирование матрицы) и не могут выполняться параллельно с вычислениями.&lt;br /&gt;
&lt;br /&gt;
Отдельно заметим, что, как уже упоминалось, первой либо последней группы пересылок можно избежать, если порядок элементов результата не важен.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; простое либо один из множителей в &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; всегда мал, ситуация усложняется. В этом случае придётся применять алгоритм Радера на межпроцессорном уровне, что связано с более сложной схемой пересылок и большим их количеством (т.к. приходится осуществлять преобразование Фурье дважды: прямое и обратное). &lt;br /&gt;
&lt;br /&gt;
В связи с этим, некоторые существующие реализации (например, Intel® MKL) просто не поддерживают этот случай. Впрочем, некоторые реализации не поддерживают большие простые делители &#039;&#039;вовсе&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
Хотя, как уже было указано, алгоритм поддаётся реализации с относительно небольшим числом пересылок, следует отметить, что вычислительна нагрузка на отдельные вычислительные ядра невелика и сопоставима с объёмом пересылок.  При этом сами пересылки достаточно объёмны (весь рабочий вектор), нелокальны (все-со-всеми), и не могут проводиться одновременно с вычислениями.  По этим причинам, ожидать от алгоритма хорошей масштабируемости не приходится, несмотря на «хорошие» свойства информационного графа.  Это и подтверждается экспериментом:&lt;br /&gt;
&lt;br /&gt;
[[Файл:TIFFTEfficiency.png|center|thumb|800px|Рисунок 3. Эффективность распараллеливания быстрого преобразования Фурье, выраженная как отношение времени работы на одном процессоре к произведению времени работы на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; процессорах на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;.  &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — порядок преобразования.]]&lt;br /&gt;
&lt;br /&gt;
При этом, однако, умеренного ускорения всё же удаётся добиться:&lt;br /&gt;
[[Файл:TIFFTSpeedup.png|center|thumb|800px|Рисунок 4.  Ускорение, получаемое при распараллеливании преобразования Фурье.  Обозначения те же, что и на рис. 3]]&lt;br /&gt;
&lt;br /&gt;
В общем, можно заметить, что, если возникла необходимость вычислять преобразование Фурье параллельно, добавление вычислительных узлов является целесообразным, однако такой ситуации следует по возможности избегать, поскольку ресурсы параллельной системы при этом расходуются крайне неэффективно.&lt;br /&gt;
&lt;br /&gt;
При измерениях использовалась реализация алгоритма, имеющаяся в библиотеке Intel® MKL.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и особенности реализации алгоритма ===&lt;br /&gt;
Как уже отмечалось выше, алгоритм существенно нелокален и плохо поддаётся распараллеливанию, а также обладает достаточно сложной структурой (почти весь состоит из рекурсивных вызовов).  В связи с этими обстоятельствами, рассчитывать на эффективное использование системных ресурсов не приходится.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
Как уже было указано, эффективность параллельной реализации крайне низкая, что связано с заложенными с саму структуру алгоритма глобальными пересылками, так что существенно повлиять на эту ситуацию без кардинальных изменений в алгоритме не представляется возможным. При работе на системе с распределённой памятью лучше всего по возможности избегать параллельного преобразования Фурье вовсе, используя по возможности параллелизм в других частях задачи.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм быстрого преобразования Фурье относится к базовым алгоритмам вычислительной математики, а потому его реализации существуют во многих библиотеках.  Перечислим некоторые из них:&lt;br /&gt;
&lt;br /&gt;
* [//software.intel.com/en-us/intel-mkl Intel® Math Kernel Library] — содержит в том числе и версию для распределённых систем.  Имеет интерфейсы для C, C++ и Fortran.&lt;br /&gt;
* [http://www.fftw.org FFTW] — содержит распределённую реализацию начиная с версии 3.3.  Имеет интерфейсы для C и Fortran.&lt;br /&gt;
* [//www.gnu.org/software/gsl GNU Scientific Library] — имеет интерфейс для C.&lt;br /&gt;
* [//developer.nvidia.com/cufft cuFFT] — реализация алгоритма для GPU от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft clFFT] — реализация алгоритма на OpenCL для CPU и GPU.  Поддерживает только &amp;lt;math&amp;gt;N = 2^p 3^q 5^r 7^s&amp;lt;/math&amp;gt;&lt;br /&gt;
* [http://www.ibm.com/systems/power/software/essl IBM® Engineering and Scientific Subroutine Library] — для процессоров IBM® POWER™, имеет интерфейсы для C, C++ и Fortran, существует версия для многопроцессорных систем.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Chachba.A&amp;diff=17568</id>
		<title>Обсуждение участника:Chachba.A</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Chachba.A&amp;diff=17568"/>
		<updated>2016-11-16T10:41:13Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п.п. 1.8, 1.9 не раскрыты--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:57, 25 октября 2016 (MSK)&lt;br /&gt;
** исправлено&lt;br /&gt;
* не указан конкретный вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:57, 25 октября 2016 (MSK)&lt;br /&gt;
** исправлено&lt;br /&gt;
* в п.п. 2.4 следует добавить описание компьютерной платформы, текст исследуемой программы.--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:41, 16 ноября 2016 (MSK) &lt;br /&gt;
&lt;br /&gt;
== Замечания к статье &amp;quot;Быстрое дискретное преобразование Фурье&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
* Перенесите пожалуйста статью в личное пространство [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:31, 11 ноября 2016 (MSK) &lt;br /&gt;
** а как это сделать? У нас нет прав на это, похоже&lt;br /&gt;
* Раздел 1.1 - в самом первом абзаце стоит вставить обозначения двух фунций - f и f с крышкой, чтобы было ясно, что куда отображается. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:31, 11 ноября 2016 (MSK) &lt;br /&gt;
** Done&lt;br /&gt;
* В общее описание алгоритма нужно вставить слова именно об алгоритме, а не только о математической задаче. В чем быстрота, за счет чего она достигается и т.д. Тут, в отличие от математического описания, не стоит увлекаться формулами. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:31, 11 ноября 2016 (MSK)&lt;br /&gt;
** Done&lt;br /&gt;
* Раздел 1.2 - существует обозначение части формулы через эпсилон, однако в следующей формуле он не встречается, странно. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:31, 11 ноября 2016 (MSK)&lt;br /&gt;
** Done&lt;br /&gt;
* Нужно пояснение, что происходит, когда N не представимо в виде произведения: применим ли алгоритм и в каких изменениях он нуждается. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:31, 11 ноября 2016 (MSK)&lt;br /&gt;
** Done&lt;br /&gt;
* Нет данных о глубине рекурсии, написать об этом явно, скорее всего, в разделе &amp;quot;вычислительное ядро алгоритма&amp;quot;. [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:31, 11 ноября 2016 (MSK)&lt;br /&gt;
** Done&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(%D0%91%D0%9F%D0%A4)&amp;diff=17567</id>
		<title>Быстрое дискретное преобразование Фурье (БПФ)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5_(%D0%91%D0%9F%D0%A4)&amp;diff=17567"/>
		<updated>2016-11-16T10:37:48Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Авторы: [[Участник:Chachba.A|Чачба А.Н.]], [[Участник:Weasel|Костоев Р.С.]] &lt;br /&gt;
&lt;br /&gt;
[[Участник:Chachba.A|Чачба А.Н.]] заполнил половину всех заполненных пунктов (а именно 1.1, 1.2, 1.6, 1.8, 1.9) и построил информационный граф алгоритма, [[Участник:Weasel|Костоев Р.С.]] написал программу и провел серию запусков на суперкомпьютере и заполнил вторую половину пунктов. Оба после заполнения исправляли найденные ошибки во всех пунктах.&lt;br /&gt;
&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразование Фурье (БПФ)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Преобразование Фурье - взаимно однозначное отображение одной функции &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; вещественной переменной, называемой таргетным сигналом, с другой функцией &amp;lt;math&amp;gt;\hat{f}&amp;lt;/math&amp;gt; вещественной переменной, называемой образом Фурье или спектром исходной функции по формуле:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\hat{f}(\omega)=\frac{1}{\sqrt{2\pi}}\int\limits_{-\infty}^{\infty}f(x)e^{-ix\omega}\,dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Дискретное преобразование Фурье, в свою очередь, есть аналог непрерывного преобразования Фурье, но для дискретного сигнала содержащего &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; отсчетов.&lt;br /&gt;
Широко применяется в цифровой обработке сигналов, теории вероятностей, криптографии и акустике.&lt;br /&gt;
Преобразование Фурье обратимо, причем обратное преобразование имеет практически ту же форму, что и прямое.&lt;br /&gt;
Преобразование Фурье имеет сложность &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, но существует быстрый вариант преобразование Фурье со сложностью &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
Такая скорость достигается благодаря применению схемы &amp;quot;разделяй и властвуй&amp;quot;. Общий вид преобразования позволяет разделить массив на несколько частей таким образом, что, применяя преобразование Фурье к отдельным его частям и объединяя специальным образом результаты (при помощи поворотных множителей и повторного применения преобразования), получать на выходе образ исходного вектора. Но благодаря разделению массива исходного размера на части суммарная асимптотическая сложность алгоритма уменьшается.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть исходный сигнал имеет значения &amp;lt;math&amp;gt;x_n,\quad n = 0,\dots,N-1&amp;lt;/math&amp;gt;, тогда дискретное прямое преобразование Фурье (ДПФ) имеет вид:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
X_k = \sum_{n=0}^{N-1}x_ne^{-\frac{2\pi i}{N}kn},\quad k = 0, \dots, N-1&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Обозначим &amp;lt;math&amp;gt; \varepsilon_{N} = e^{-\frac{2\pi i}{N}}&amp;lt;/math&amp;gt;, тогда ДПФ можно перезаписать в матричной форме:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\bar X = A\bar x&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где матрица &amp;lt;math&amp;gt;A = \{\varepsilon_{N}^{(i - 1)(j - 1)}\}_{i,j=1}^{N}&amp;lt;/math&amp;gt;&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Пусть, для простоты &amp;lt;math&amp;gt; N = km&amp;lt;/math&amp;gt;, тогда рекурсивная реализация преобразования Фурье, за счет &amp;lt;math&amp;gt;{k}&amp;lt;/math&amp;gt; рекурсий на первом этапе и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; на последнем этапе, имеет суммарную сложность &amp;lt;math&amp;gt;O(Nk + Nm + km) = O(N(k + m))&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
В случае, например &amp;lt;math&amp;gt;N = 2^n&amp;lt;/math&amp;gt; сложность БПФ составляет &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В общем же случае, когда &amp;lt;math&amp;gt;N = \prod_{i=1}^np_i&amp;lt;/math&amp;gt; сложность БПФ составляет &amp;lt;math&amp;gt; O(N(\sum_{i=1}^np_i))&amp;lt;/math&amp;gt;. Так как рекурсия применяется каждый раз при отщеплении одного множителя, то можно заключить, что глубина рекурсии алгоритма составляет &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-число множителей в факторизации &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В свою очередь, если число &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; не факторизуется в произведение простых множителей (то есть &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; - простое число), то данный алгоритм не применим. На замену ему приходит алгоритм Радера, имеющий худшую асимптотику, нежели обычный БПФ, но лучшую чем &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;. Также алгоритм Радера потребует дополнительного взаимодействия процессоров, что приведет к ухудшению эффективности.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Макроструктура БПФ для случая &amp;lt;math&amp;gt;N = km&amp;lt;/math&amp;gt; описывается рекурсивно:&lt;br /&gt;
# &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; независимых преобразований векторов меньшей размерности &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;&lt;br /&gt;
#Умножение элементов на поворотные коэффициенты (&amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; умножений)&lt;br /&gt;
# &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; обратных преобразований векторов размерностей &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Рекурсивный метод для случая &amp;lt;math&amp;gt;N = 2^k&amp;lt;/math&amp;gt;, без оптимизации на C++:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;#include &amp;lt;vector&amp;gt;&lt;br /&gt;
#include &amp;lt;complex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using namespace std;&lt;br /&gt;
&lt;br /&gt;
typedef complex&amp;lt;double&amp;gt; cd;&lt;br /&gt;
typedef vector&amp;lt;cd&amp;gt; vcd;&lt;br /&gt;
&lt;br /&gt;
vcd fft(const vcd &amp;amp;as) { &lt;br /&gt;
  int n = as.size();&lt;br /&gt;
  if (n == 1) return vcd(1, as[0]);&lt;br /&gt;
&lt;br /&gt;
  vcd w(n); // Calculate roots&lt;br /&gt;
  for (int i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
    double alpha = 2 * M_PI * i / n;&lt;br /&gt;
    w[i] = cd(cos(alpha), sin(alpha));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  vcd A(n / 2), B(n / 2);&lt;br /&gt;
  for (int i = 0; i &amp;lt; n / 2; i++) {&lt;br /&gt;
    A[i] = as[i * 2];&lt;br /&gt;
    B[i] = as[i * 2 + 1];&lt;br /&gt;
  }&lt;br /&gt;
  vcd Av = fft(A);&lt;br /&gt;
  vcd Bv = fft(B);&lt;br /&gt;
  vcd res(n);&lt;br /&gt;
  for (int i = 0; i &amp;lt; n; i++)&lt;br /&gt;
    res[i] =   Av[i % (n / 2)] +&lt;br /&gt;
        w[i] * Bv[i % (n / 2)];&lt;br /&gt;
  return res;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
В последовательном варианте можно бороться за улучшение константы сложности, предподсчитав значения соответствующих коэффициентов.&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Алгоритм состоит из трех этапов, следовательно если &amp;lt;math&amp;gt;N = \prod_{i = 1}^np_i&amp;lt;/math&amp;gt;, то общая сложность составляет порядка &amp;lt;math&amp;gt;O(N\sum_{i=1}^np_i)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Пусть существует некоторое фиксированное число &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;, такое что:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
p_i \leq P \quad \forall i = 1...n,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
тогда, учитывая, что &amp;lt;math&amp;gt;n \leq \log_{P}{N}&amp;lt;/math&amp;gt;, сложность алгоритма можно записать в виде &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[Файл:FourierGraph2.png|center|thumb|640px|&#039;&#039;&#039;Рисунок 1.&#039;&#039;&#039; Информационный граф преобразования Кули-Тьюки для &amp;lt;math&amp;gt;N = km = 25, k = 5, m = 5&amp;lt;/math&amp;gt;. Желтые вершины - умножение на поворотные множители.]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
В силу независимости всех рекурсивных вызовов и того факта, что путь максимальной длины в графе (так называемый критический путь) имеет порядок &amp;lt;math&amp;gt;O(\log{N})&amp;lt;/math&amp;gt;, можно утверждать, что параллельная сложность алгоритма составит &amp;lt;math&amp;gt;O(\log{N})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Замечание&#039;&#039;: формально основание логарифма на каждом рекурсивном шаге зависит от множителя в факторизации &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, потому использована О-символика.&lt;br /&gt;
&lt;br /&gt;
При условии ограниченности количества доступных вычислительных узлов рекомендуется выбирать множители в разложении числа $N$ близкими к числу доступных узлов, таким образом доступные ресурсы будут использованы максимально эффективно.&lt;br /&gt;
&lt;br /&gt;
Итого алгоритм относится к логарифмическому классу алгоритмов по параллельной сложности.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
Чаще всего для обработки сигналов в качестве входных данных для БПФ подается вектор размерности &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; вещественных элементов. Но БПФ работает и для случая элементов над комплексным полем. Таким образом, например, можно экономить на количестве применений БПФ в некоторой конкретной задаче путем приведения двух векторов вещественных чисел к одному вектору комплексных чисел с вещественной частью равной первому вектору и комплексной частью равной второму вектору:&lt;br /&gt;
&amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; - исходные сигналы. Обозначим &amp;lt;math&amp;gt;z = x + iy&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;F(k, x)&amp;lt;/math&amp;gt; - это &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;-ый коэффициент образа Фурье вектора &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;. Тогда:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, x) = \frac{F(k, z) + F(k, \bar z)}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, y) = -i\frac{F(k, z) - F(k, \bar z)}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Учитывая, что &amp;lt;math&amp;gt;F(k, \bar z) = \overline{F(N - k, z)}&amp;lt;/math&amp;gt;, получаем:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, x) = \frac{F(k, z) + \overline{F(N-k, z)}}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;F(k, y) = -i\frac{F(k, z) - \overline{F(N-k,z)}}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Такой способ позволяет приблизительно в два раза уменьшить количество вычислений.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
Вектор размерности &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; комплексных чисел - спектр исходного сигнала.&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Матрица Вандермонда преобразования &amp;lt;math&amp;gt;A = \{e^{-\frac{2\pi i}{N}(i - 1)(j - 1)}\}_{i,j=1}^{N}&amp;lt;/math&amp;gt; такая, что:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
A^{-1} = \frac{1}{N}A^*&lt;br /&gt;
&amp;lt;/math&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;
[[Файл:Time.PNG|center|thumb|640px|&#039;&#039;&#039;Рисунок 2.&#039;&#039;&#039; Время выполнения параллельной реализации БПФ в секундах. Size - число элементов массива. Р - число использованных процессоров.]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Efff.PNG|center|thumb|640px|&#039;&#039;&#039;Рисунок 3.&#039;&#039;&#039; Эффективность параллельной реализации БПФ. Size - число элементов массива. Р - число использованных процессоров. Эффективность в пределах от 0 до 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;
* Самая популярная библиотека для работы с преобразованиями Фурье это [http://www.fftw.org FFTW]&lt;br /&gt;
* Реализация от Intel в рамках [//software.intel.com/en-us/intel-mkl Math Kernel Library]&lt;br /&gt;
* Реализация ДПФ на графических картах от NVidia - [//developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
* Бахвалов Н. С.,  Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.&lt;br /&gt;
* Описание алгоритма на Википедии: [https://ru.wikipedia.org/wiki/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5 Быстрое преобразование Фурье]&lt;br /&gt;
* Материалы лекций по &amp;quot;Алгоритмам и структурам данных&amp;quot; Школы Анализа Данных Яндекса&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Ivanov.kir.m&amp;diff=17566</id>
		<title>Обсуждение участника:Ivanov.kir.m</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Ivanov.kir.m&amp;diff=17566"/>
		<updated>2016-11-16T10:36:06Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
=== Неправильная структура статьи ===&lt;br /&gt;
* привести в соответствие 1 и 2 часть описания--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:31, 25 октября 2016 (MSK)&lt;br /&gt;
** Выполнено&lt;br /&gt;
* «рекурсивное описание» вынести в подпункт к основному пункту содержания--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:31, 25 октября 2016 (MSK)&lt;br /&gt;
** Выполнено&lt;br /&gt;
&lt;br /&gt;
=== Отсутствующие части ===&lt;br /&gt;
* отсутствуют п.п. 2.2, 2.3, 2.5, 2.6--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:31, 25 октября 2016 (MSK)&lt;br /&gt;
** Согласно постановке первого домашнего задания по курсу Суперкомпьютерное моделирование и технологии ([http://parallel.ru/ss16 Сайт курса]) пункты 2.1, 2.2, 2.3, 2.5, 2.6 по общей структуре статьи (см. [http://algowiki-project.org/ru/%D0%A1%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F_%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2 Структура описания свойств алгоритмов]) являются необязательными к заполнению (см. [http://parallel.ru/sites/default/files/info/education/superk-modelir-1-2016.pdf Первая лекция по курсу СкМиТ] слайд 62)&lt;br /&gt;
* отсутствует вклад каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:31, 25 октября 2016 (MSK)&lt;br /&gt;
** Указали в шапке статьи&lt;br /&gt;
&lt;br /&gt;
=== Замечания по тексту ===&lt;br /&gt;
* п.п. 1.9 и 1.10 (в нумерации автора) не раскрыты--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:31, 25 октября 2016 (MSK)&lt;br /&gt;
** Исправлено&lt;br /&gt;
* в  п.п. 2.4 следует добавить описание компьютерной платформы--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:35, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
== Замечания к статье Быстрое дискретное преобразование Фурье ==&lt;br /&gt;
&lt;br /&gt;
* В разделе &amp;quot;Общее описание алгоритма&amp;quot; следует вставить именно описание алгоритма. Потому что смысл алгоритма проясняется только в следующем разделе, а желательно иметь его общее (с минимумом формул) описание во введении [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:12, 11 ноября 2016 (MSK)&lt;br /&gt;
** Сделали&lt;br /&gt;
* Стоит добавить слова о том, что происходит, когда размер массива не представим в виде произведения - можно ли тогда применять алгоритм, и если да, то какие изменения следует сделать [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:12, 11 ноября 2016 (MSK)&lt;br /&gt;
** Сделали&lt;br /&gt;
* Не вполне понятна логика использования курсива и полужирного начертания [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:12, 11 ноября 2016 (MSK)&lt;br /&gt;
** Сделали&lt;br /&gt;
* Не хватает данных о глубине рекурсии при использовании алгоритма [[Участник:Kronberg|Kronberg]] ([[Обсуждение участника:Kronberg|обсуждение]]) 13:12, 11 ноября 2016 (MSK)&lt;br /&gt;
** Сделали&lt;/div&gt;</summary>
		<author><name>IgorS</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:Ivanov.kir.m/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17565</id>
		<title>Участник:Ivanov.kir.m/Быстрое дискретное преобразование Фурье</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:Ivanov.kir.m/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=17565"/>
		<updated>2016-11-16T10:32:55Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Алгоритм одномерного преобразования Фурье для действительных чисел&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O (N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O (\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; действительных чисел&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;\lfloor N/2 \rfloor+1&amp;lt;/math&amp;gt; комплексных чисел&lt;br /&gt;
}}&lt;br /&gt;
Авторы описания [[Участник:Ivanov.kir.m | К.М.Иванов]], [[Участник:Pumpkin | А.C.Сапин]] Вклад каждого участника равномерный, поскольку каждый пункт вычитывался и исправлялся обоими участниками. При сильном приближении можно сказать,что больший вклад [[Участник:Ivanov.kir.m | К.М.Иванов]] внес в заполнение теоретической части, а [[Участник:Pumpkin | А.C.Сапин]] в заполнение практической части. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Быстрое преобразование Фурье (БПФ, FFT)&#039;&#039;&#039; — алгоритм быстрого  вычисления дискретного преобразования Фурье (ДПФ). То есть, алгоритм вычисления за количество действий, меньшее чем &amp;lt;math&amp;gt;O(N^{2})&amp;lt;/math&amp;gt;, требуемых для прямого (по формуле) вычисления ДПФ. Иногда под БПФ понимается один из быстрых алгоритмов, называемый алгоритмом прореживания по частоте/времени или алгоритмом по основанию 2, имеющий сложность &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt;. Cуществует несколько различных алгоритмов для вычисления ДПФ считающимся быстрым преобразование Фурье:&lt;br /&gt;
* Алгоритм Кули-Тьюки [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm]&lt;br /&gt;
* Алгоритм Гуда-Томаса [https://en.wikipedia.org/wiki/Prime-factor_FFT_algorithm]&lt;br /&gt;
* Алгоритм Бруна  [https://en.wikipedia.org/wiki/Bruun%27s_FFT_algorithm]&lt;br /&gt;
* Алгоритм Блюштейна [https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm]&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Быстрое дискретное преобразование Фурье&#039;&#039;&#039; (&#039;&#039;БПФ&#039;&#039;) - популярный в современном мире математический метод преобразования вектора/матрицы комплексных/действительных чисел - &#039;&#039;сигнала&#039;&#039; - в вектор/матрицу комплексных чисел - &#039;&#039;спектр&#039;&#039;. С математической точки зрения преобразование сигнала &amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; (как функции по времени &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt;) в спектр &amp;lt;math&amp;gt;\hat{s}&amp;lt;/math&amp;gt; описывается формулой&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\hat{s}(\omega)=\frac{1}{\sqrt{2\pi}}\int\limits_{-\infty}^{\infty}s(t)e^{-it\omega}\,dt&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В отличии от своего классического аналога БПФ имеет сложность &amp;lt;math&amp;gt;O(N\log{N})&amp;lt;/math&amp;gt;, а не &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, это достигается благодаря разделению исходного вектора на несколько частей, так чтобы применив к этим частям БПФ и объединив результаты путем умножения на поворотные множители и дополнительного БПФ, можно было получить преобразование Фурье исходного вектора - принцип &amp;quot;разделяй и властвуй&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Область применения данного метода простирается от обработки аудио информации до собственно спектрального анализа.&lt;br /&gt;
Поскольку в общем случае алгоритм БПФ применим к произвольным векторам чисел, выделяют отдельные подзадачи, для которых можно ввести дополнительную оптимизацию. Одной из таких подзадач является быстрое преобразование Фурье вектора действительных чисел, которому и посвящена данная статья. В качестве тестируемой реализации был выбран алгоритм Кули-Тьюки в библиотеке FFTW, написанной на языке C.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание&#039;&#039;&#039;: Поскольку общие описание алгоритма БПФ весьма сложно, в некоторых пунктах данная статья будет ссылаться на простой случай - алгоритм Кули-Тьюки быстрого преобразование Фурье для векторов &#039;&#039;с размерностью равной степени двойки&#039;&#039;. Отличительной особенностью данного алгоритма является то, что он обходится без использования специфических приемов, использующихся именно для степеней четверки, восьмерки и т.п. Однако благодаря тому, что на вход данному алгоритму подается вектор чисто вещественных чисел, выходной вектор удовлетворяет &#039;&#039;эрмитовой избыточности&#039;&#039; (&#039;&#039;Hermitian redundancy&#039;&#039;) , т.е.  &amp;lt;math&amp;gt;out[i]&amp;lt;/math&amp;gt; является сопряженным с &amp;lt;math&amp;gt;out[n-i]&amp;lt;/math&amp;gt;. Это обстоятельство позволяет достичь роста скорости и снижения затрат памяти примерно в 2 раза по сравнению с &#039;&#039;комплексным&#039;&#039; аналогом алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Входные данные: вектор действительных чисел &amp;lt;math&amp;gt;x = (x_1,x_2,...,x_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выходные данные: вектор комплексных чисел &amp;lt;math&amp;gt;X = (X_1,X_2,...,X_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Дискретное преобразование Фурье задается следующей формулой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;X_k = \sum_{n=0}^{N-1} x_n e^{-\frac{2\pi i}{N} nk}, k = \overline{0,N-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В простейшем случае(для степеней двойки) алгоритм Кули-Тьюки рассчитывает ДПФ для четных &amp;lt;math&amp;gt;(x_{2m}=x_0, x_2, \ldots, x_{N-2})&amp;lt;/math&amp;gt; и нечетных элементов отдельно &amp;lt;math&amp;gt;(x_{2m+1}=x_1, x_3, \ldots, x_{N-1})&amp;lt;/math&amp;gt;. Затем результаты рассчетов объединяются для получения ДПФ всей последовательности: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{matrix} X_k &amp;amp; =&lt;br /&gt;
&amp;amp; \sum \limits_{m=0}^{N/2-1} x_{2m}e^{-\frac{2\pi i}{N} (2m)k}   +   \sum \limits_{m=0}^{N/2-1} x_{2m+1} e^{-\frac{2\pi i}{N} (2m+1)k}&lt;br /&gt;
  \end{matrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, получается рекурсивный алгоритм, работающий по принципу [https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0) разделяй и влавствуй].&lt;br /&gt;
&lt;br /&gt;
=== Рекурсивное описание ===&lt;br /&gt;
Алгоритм:&lt;br /&gt;
# Входной вектор &amp;lt;math&amp;gt;a = (a_0,a_2,...,a_{N-1})&amp;lt;/math&amp;gt; преобразуется в матрицу &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; размера &amp;lt;math&amp;gt;n_1 \times n_2 &amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;N=n_1 \cdot n_2&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_1 &amp;lt; n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; A = \begin{pmatrix}&lt;br /&gt;
  a_0 &amp;amp; a_1 &amp;amp; \cdots &amp;amp; a_{n_1-1} \\&lt;br /&gt;
  a_{n_1} &amp;amp; a_{n_1+1} &amp;amp; \cdots &amp;amp; a_{2n_1-1} \\&lt;br /&gt;
  \vdots  &amp;amp; \vdots  &amp;amp; \ddots &amp;amp; \vdots  \\&lt;br /&gt;
  a_{(n_2-1)\cdot n_1} &amp;amp; a_{(n_2-1)\cdot n_1+1} &amp;amp; \cdots &amp;amp; a_{n_2\cdot n_1-1} &lt;br /&gt;
 \end{pmatrix} &amp;lt;/math&amp;gt;&lt;br /&gt;
# К каждой строке полученной матрицы применяется быстрое дискретное преобразование Фурье (БПФ) порядка &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt;&lt;br /&gt;
# Каждый элемент полученный после применения БПФ умножается на &#039;&#039;поворотные множители&#039;&#039; &amp;lt;math&amp;gt;exp (2 \pi i(m-1)(j-1)/N)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; - номер строки, а &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; - номер столбца&lt;br /&gt;
# Полученная после шагов 1-2 матрица &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; транспонируется&lt;br /&gt;
# К каждой строке матрицы &amp;lt;math&amp;gt;A^T&amp;lt;/math&amp;gt; применяется БПФ порядка &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как алгоритм реализует принцип &amp;quot;разделяй и властвуй&amp;quot; глубина рекурсии составляет &amp;lt;math&amp;gt;O (\log N)&amp;lt;/math&amp;gt; от числа входных элементов.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; Как правило все &#039;&#039;поворотные множители&#039;&#039; вычисляются заранее и хранятся в специальном массиве.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; В общем случае &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; разбивается на &amp;lt;math&amp;gt;n_1 \cdot n_2&amp;lt;/math&amp;gt; так, чтобы &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; сами не являлись простыми числами и разлагались на множители не являющиеся простыми числами.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Замечание:&#039;&#039;&#039; В случае простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; оно дополняется(как правило единицией) до непростого числа. В процессе слияния добавленные значения отбрасываются.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В случае размерности входа равной степени двойки, вычислительным ядром алгоритма является так называемая, &amp;quot;&#039;&#039;бабочка&#039;&#039;&amp;quot;. В простейшем случае &amp;quot;&#039;&#039;бабочка&#039;&#039;&amp;quot; представляет из себя двухточечное преобразование. Рассмотрим этот случай:&lt;br /&gt;
&lt;br /&gt;
На вход алгоритму подается двухэлементный вектор ‒ &amp;lt;math&amp;gt; v = (v[0], v[1]) &amp;lt;/math&amp;gt;. Тогда для вычисления будут происходить по следующим формулам:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[0] = W_2^0 v[0] + W_2^0 v[1] = v[0] + W_2^0 v[1] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[1] = W_2^0 v[0] + W_2^1 v[1] = v[0] + W_2^1 v[1] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Данный процесс удобно изобразить с помощью следующей схемы:&lt;br /&gt;
&lt;br /&gt;
[[file:Fftbutterfly.png | Рис. 1. &amp;quot;&#039;&#039;Бабочка&#039;&#039;&amp;quot; для двумерного входа ]]&lt;br /&gt;
&lt;br /&gt;
Для 4-х элеметного вектора &amp;lt;math&amp;gt;v=(v[0],v[1],v[2],v[3])&amp;lt;/math&amp;gt;, алгоритм строится похожим образом. Сначала создаются простейшие &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot;, а потом их результаты соединяются с противоположеной &amp;quot;бабочкой&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[0]=v[0]+W_2^0 v[2]+W_4^0(v[1]+W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[1]=v[0]-W_2^0 v[2]+W_4^1(v[1]-W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[2]=v[0]+W_2^0 v[2]-W_4^0(v[1]+W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V[3]=v[0]-W_2^0 v[2]-W_4^1(v[1]-W_2^0 v[3])&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Схема в таком случае будет выглядеть следующим образом:&lt;br /&gt;
&lt;br /&gt;
[[file:Fftbutterfly4.png| Рис. 2. &amp;quot;&#039;&#039;Бабочка&#039;&#039;&amp;quot; для четырёхмерного входа ]]&lt;br /&gt;
&lt;br /&gt;
Для случая, когда вход не является степенью двойки, &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot; будут &amp;quot;&#039;&#039;несимментричными&#039;&#039;&amp;quot;, но в остальном вычисления будут проходить схожим образом.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Для исходного вектора &amp;lt;math&amp;gt;a = (a_1,a_2,...,a_N)&amp;lt;/math&amp;gt; размерности &amp;lt;math&amp;gt;N = n_1 \cdot n_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n_1 &amp;lt; n_2&amp;lt;/math&amp;gt; БПФ представляется как:&lt;br /&gt;
#&amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; БПФ порядка &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; &lt;br /&gt;
#&amp;lt;math&amp;gt;n_1 \cdot n_2&amp;lt;/math&amp;gt; умножение комплексных чисел &lt;br /&gt;
#&amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; БПФ порядка &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Схема организации состоит в том, что на каждом шаге &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; для выполнения &amp;quot;&#039;&#039;бабочки&#039;&#039;&amp;quot; все элементы разбиваются на &amp;lt;math&amp;gt;n_{i_1}&amp;lt;/math&amp;gt; векторов по &amp;lt;math&amp;gt;n_{i_2}&amp;lt;/math&amp;gt; элементов, причем &amp;lt;math&amp;gt;n_{i_1} \cdot n_{i_2} = n_i&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;n_i&amp;lt;/math&amp;gt; длина входного вектора на текущем шаге &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;. В случае если такое разбиение невозможно, по причине того что &amp;lt;math&amp;gt;n_i&amp;lt;/math&amp;gt; простое число, исходный вектор на текущем шаге дополняется элементом. В зависимости от номера шага, разница координат для каждой пары элементов увеличивается соразмерно разбиению  &amp;lt;math&amp;gt;(n_{i_1},n_{i_2})&amp;lt;/math&amp;gt;.&lt;br /&gt;
При этом результат суммы записывается в элемент с меньшим номером, а результат вычитания с последующим умножением - в элемент с большим.&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Быстрое дискретное преобразование Фурье выполнимо за &amp;lt;math&amp;gt;O(N(n_1+\cdots+n_m))&amp;lt;/math&amp;gt; действий при &amp;lt;math&amp;gt;N=n_1n_2\cdots n_m&amp;lt;/math&amp;gt; (в простом случае, при &amp;lt;math&amp;gt;N=2^m&amp;lt;/math&amp;gt; необходимо &amp;lt;math&amp;gt;O(N\log_2(N))&amp;lt;/math&amp;gt; действий). Дискретное преобразование Фурье преобразует вектор &amp;lt;math&amp;gt;a = (a_0, \dots, a_{N-1})&amp;lt;/math&amp;gt; в вектор комплексных чисел &amp;lt;math&amp;gt;b = (b_0, \dots, b_{N-1})&amp;lt;/math&amp;gt;, такой, что &amp;lt;math&amp;gt;b_i=\sum_{j=0}^{N-1}a_j\varepsilon^{ij}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\varepsilon^n=1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\varepsilon^k\neq 1&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;0&amp;lt;k&amp;lt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной шаг алгоритма состоит в сведении задачи для &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; чисел к задаче для &amp;lt;math&amp;gt;n_1=N/n_2&amp;lt;/math&amp;gt; числам, где &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; — делитель &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть мы уже умеем решать задачу для &amp;lt;math&amp;gt;N/n_2&amp;lt;/math&amp;gt; чисел. Применим преобразование Фурье к векторам &amp;lt;math&amp;gt;a_i,a_{n_2+i}, \dots, a_{n_2(n_1-1)+i}&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;i=0,1,\dots,n_2-1&amp;lt;/math&amp;gt;. Покажем теперь, что за &amp;lt;math&amp;gt;O(Np)&amp;lt;/math&amp;gt; действий можно решить исходную задачу. Заметим, что &amp;lt;math&amp;gt;b_i=\sum_{j=0}^{n_2-1} \varepsilon^{ij} \left(\sum_{k=0}^{n_1-1}a_{kn_2+j}\varepsilon^{kin_2}\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Выражения в скобках нам уже известны — это &amp;lt;math&amp;gt;(i\mod p)&amp;lt;/math&amp;gt;-тое число после преобразования Фурье &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;-го вектора. Таким образом, для вычисления каждого &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; нужно &amp;lt;math&amp;gt;O(n_2)&amp;lt;/math&amp;gt; действий, а для вычисления всех &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; всего &amp;lt;math&amp;gt;O(Nn_2)&amp;lt;/math&amp;gt; действий, что и требовалось.&lt;br /&gt;
&lt;br /&gt;
В общем случае:&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;4N&amp;gt;2^k\ge2N&amp;lt;/math&amp;gt;. Заметим, что тогда &amp;lt;math&amp;gt;b_i=\varepsilon^{-i^2/2}\sum_{j=0}^{N-1}\varepsilon^{(i+j)^2/2}\varepsilon^{-j^2/2}a_j&amp;lt;/math&amp;gt;. Обозначим &amp;lt;math&amp;gt;\bar{a}_i=\varepsilon^{-i^2/2}a_i&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;\bar{b}_i=\varepsilon^{i^2/2}b_i&amp;lt;/math&amp;gt;,   &amp;lt;math&amp;gt;c_i=\varepsilon^{(2N-2-i)^2/2}&amp;lt;/math&amp;gt;. Тогда &amp;lt;math&amp;gt;\bar{b}_i=\sum_{j=0}^{2N-2-i}\bar{a}_jc_{2N-2-i-j}&amp;lt;/math&amp;gt;, если положить &amp;lt;math&amp;gt;\bar{a}_i=0&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;i\ge N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом задача сведена к вычислению &#039;&#039;свёртки&#039;&#039;, а это можно сделать с помощью трёх преобразований Фурье для &amp;lt;math&amp;gt;2^k&amp;lt;/math&amp;gt; элементов. Выполняем прямое преобразование Фурье для &amp;lt;math&amp;gt;(\bar{a_0}, \cdots, \bar{a}_{2^k-1})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(c_1,\cdots,c_{2^k-1})&amp;lt;/math&amp;gt;, перемножаем поэлементно результаты и выполняем обратное преобразование Фурье.&lt;br /&gt;
&lt;br /&gt;
Вычисления всех &amp;lt;math&amp;gt;\bar{a}_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_i&amp;lt;/math&amp;gt; требуют &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий, три преобразования Фурье требуют &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt; действий, перемножение результатов преобразований Фурье требует &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий, вычисление всех &amp;lt;math&amp;gt;b_i&amp;lt;/math&amp;gt; зная значения свертки требует &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; действий. Итого для дискретного преобразования Фурье требуется &amp;lt;math&amp;gt;O(N\log(N))&amp;lt;/math&amp;gt; действий для любого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[Файл:InfoGraph Ivanov Sapin.png |center|thumb|800px| Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 21&amp;lt;/math&amp;gt;. Исходные данные обозначены зеленым, результат — синим.  Преобразования Фурье для &amp;lt;math&amp;gt;n_1 = 7&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2 = 3 \text{   } (N = n_1 \cdot n_2)&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты (множители) представлено коричневыми ромбами.]]&lt;br /&gt;
Как видно из рисунка, размер этого графа линейно-логарифмический, а формулы дуг имеют экспоненциальные компоненты.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Если считать только главные члены выражений, то простой алгоритм Кули-Тьюки имеет критический путь, состоящий из &amp;lt;math&amp;gt;log(N)&amp;lt;/math&amp;gt; операций комплексного сложения/вычитания и &amp;lt;math&amp;gt;log(N)&amp;lt;/math&amp;gt; операций комплексного умножения (основание &amp;lt;math&amp;gt;log&amp;lt;/math&amp;gt; целиком зависит от выбранного на каждом шаге алгоритма разбиения). Здесь стоит отметить, что все рекурсивные вызовы БПФ на каждом шаге выполняются независимо, это приводит к тому, что их можно распределить по доступным вычислительным узлам. Аналогичная ситуация обстоит и с умножением на поворотные множетели, что позволяет независимо присоединять его к любому шагу. Таким образом алгоритм БПФ имеет высокий ресурс параллелизма, а алгоритм БПФ в общем случае может быть отнесён к &#039;&#039;логарифмическому&#039;&#039; классу по параллельной сложности. &lt;br /&gt;
&lt;br /&gt;
Кроме того, параллелизм БПФ можно увеличить, если брать &amp;lt;math&amp;gt;n_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n_2&amp;lt;/math&amp;gt; по возможности близкими к кратным доступному количеству вычислительных узлов (а не брать просто один из множителей близким к нулю, как в традиционной реализации), это позволит равномерно распределить работу между вычислительными узлами, что сильно повысит эффективность алгоритма благодаря лишь одной внутренней пересылке данных.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; вектор действительных/комплексных чисел &amp;lt;math&amp;gt;a = (a_1,a_2,...,a_N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; вектор комплексных чисел &amp;lt;math&amp;gt;b = (b_1,b_2,...,b_{\lfloor N/2 \rfloor+1})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Есть несколько ситуаций, при которых количество потребляемой памяти для хранения входного вектора &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и выходного вектора &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; тможет быть снижено вдвое: &lt;br /&gt;
# Если &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; — вектор действительных чисел, тогда &amp;lt;math&amp;gt;b = E\bar{b}&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; — вектор чисто-мнимых чисел, тогда &amp;lt;math&amp;gt;b = -E\bar{b}&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a = E\bar{a}&amp;lt;/math&amp;gt;, тогда &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; — вектор действительных чисел;&lt;br /&gt;
# Если &amp;lt;math&amp;gt;a = -E\bar{b}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; — вектор чисто-мнимых чисел.&lt;br /&gt;
&lt;br /&gt;
Где матрица &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  E = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0 \\ &lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 1 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 1 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \ddots &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 0 &amp;amp; 1 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
     0 &amp;amp; 1 &amp;amp; 0 &amp;amp; \cdots &amp;amp; 0 &amp;amp; 0&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Таким образом в общем у алгоритма БПФ в случае неограниченных ресурсов:&lt;br /&gt;
*последовательная сложность - &#039;&#039;линейно-логарифмическая&#039;&#039;&lt;br /&gt;
*параллельная сложность  - &#039;&#039;логарифмическая&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
При этом алгоритм полностью детерминирован.&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
В качестве тестируемой реализации была выбрана библиотека FFTW, а именно функция, выполняющая БПФ одномерного вектора действительных чисел.&lt;br /&gt;
==  Особенности последовательной реализации алгоритма ==&lt;br /&gt;
Простейший вариант алгоритма для степеней двойки на языке C:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
#include &amp;lt;complex.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
typedef double complex cplx;&lt;br /&gt;
 &lt;br /&gt;
void _fft(cplx *buf, cplx *out, int n, int step)&lt;br /&gt;
{&lt;br /&gt;
    if (step &amp;lt; n) {&lt;br /&gt;
	_fft(out, buf, n, step * 2); //рекурсивный рассчет&lt;br /&gt;
	_fft(out + step, buf + step, n, step * 2);&lt;br /&gt;
 &lt;br /&gt;
	for (int i = 0; i &amp;lt; n; i += 2 * step) {&lt;br /&gt;
	    cplx t = cexp(-I * PI * i / n) * out[i + step];&lt;br /&gt;
	    buf[i / 2]     = out[i] + t;&lt;br /&gt;
	    buf[(i + n)/2] = out[i] - t;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
void fft(cplx *buf, int n)&lt;br /&gt;
{&lt;br /&gt;
    cplx out[n];&lt;br /&gt;
    for (int i = 0; i &amp;lt; n; i++) out[i] = buf[i];&lt;br /&gt;
 &lt;br /&gt;
    _fft(buf, out, n, 1);&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;
Масштабируемость алгоритма проверялась с помощью тестовой программы расположенной по [https://github.com/alesapin/parallel_fftw ссылке]. Для эксперимента использовались:&lt;br /&gt;
* Компилятор mpicc&lt;br /&gt;
* Библиотека OpenMPI версии 1.68&lt;br /&gt;
* Библиотека FFTW 3.3.5&lt;br /&gt;
Алгоритм тестировался на входных векторах размера &amp;lt;math&amp;gt;2^{15}&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;2^{26}&amp;lt;/math&amp;gt;. При этом использовались от 8 до 128 процессоров с шагом степени двойки.&lt;br /&gt;
&lt;br /&gt;
[[File:Fftw time good cr.png  |center|thumb|800px| Замеры времени работы параллельной реализации FFTW для входного вектора действительных чисел. По оси Data Size указано &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; -- число входных элементов. По оси Procs Num указано &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; -- число задействованных процессоров. По оси Elapsed Time -- время затраченное на выполненине алгоритма в секундах.]]&lt;br /&gt;
&lt;br /&gt;
[[File:Fftw efficency good cr.png |center|thumb|800px| Эффективность параллельной реализации FFTW для входного вектора действительных чисел. По оси Data Size указано &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; -- число входных элементов. По оси Procs Num указано &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; -- число задействованных процессоров. По оси Effeciency -- эффективность алгоритма в процентах.]]&lt;br /&gt;
&lt;br /&gt;
Из графиков можно сделать следующие выводы:&lt;br /&gt;
* Значительное увеличение числа процессоров не дает значимого уменьшения времени исполнения, что отражено и на графике эффективности. Это означает, что время, в основном, расходуется на пересылки данных, а не на рассчет.&lt;br /&gt;
* При большом количестве элементов и маленьком числе процессоров наблюдается сверхлинейное ускорение, что связано с маленьким число пересылок и активным использованием кеша.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Наиболее известной библиотекой для выполнения различных вариантов быстрого преобразования Фурье является [http://www.fftw.org/ FFTW], разработанная Матео Фриго и Стивеном Джонсоном в Массачусетском технологическом институте. Для преобразования небольших объемов данных используется алгоритм [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Кули - Тьюки], в случае входа больших размеров используется алгоритм [https://en.wikipedia.org/wiki/Rader%27s_FFT_algorithm Радера] или [https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm Блюштейна]. Библиотека считается самой быстрой реализацией БПФ и на входах любых размеров и размерностей сохраняет ассимптотическую сложность &amp;lt;math&amp;gt;O(N\cdot log(N))&amp;lt;/math&amp;gt;. Также, существует версия FFTW с поддержкой [http://www.fftw.org/doc/Distributed_002dmemory-FFTW-with-MPI.html#Distributed_002dmemory-FFTW-with-MPI MPI], которая позволяет с объемами данных не умещающимися в память.&lt;br /&gt;
&lt;br /&gt;
FFTW изначально написана на языке C, однако имеет API для нескольких языков, в том числе Fortran и Ada. Открытая версия библиотеки распространяется под лицензией [https://en.wikipedia.org/wiki/GNU_General_Public_License GNU General Public License]. Также существует реализация для коммерческого использования распространяемая под лицензией [https://en.wikipedia.org/wiki/MIT MIT].&lt;br /&gt;
&lt;br /&gt;
Существует несколько реализаций алгоритма, для рассчета на GPU:&lt;br /&gt;
* [https://developer.nvidia.com/cufft cuFFT] для рассчета на графических ускорителях от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft/ clFFT] для рассчета на GPU и CPU.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Википедия [Электронный ресурс]. Тема: Быстрое преобразование Фурье – Электрон. дан. – URL [https://ru.wikipedia.org/wiki/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5 Быстрое преобразование Фурье] (дата обращения 17.09.2016)&lt;br /&gt;
&lt;br /&gt;
[2] Бахвалов Н. С.,  Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.&lt;br /&gt;
[[en:Fast Discrete Fourier Transform]]&lt;br /&gt;
&lt;br /&gt;
[3] FFTW [Электронный ресурс]. Тема: One-Dimensional DFTs of Real Data – Электрон. дан. – URL [http://www.fftw.org/doc/One_002dDimensional-DFTs-of-Real-Data.html#One_002dDimensional-DFTs-of-Real-Data One-Dimensional DFTs of Real Data] (последняя дата обращения 14.19.2016)&lt;br /&gt;
&lt;br /&gt;
[4] FFTW [Электронный ресурс]. – Электрон. дан. – URL [http://www.fftw.org/ FFTW Lib] (последняя дата обращения 14.19.2016)&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:NikoSergAlex&amp;diff=17563</id>
		<title>Обсуждение участника:NikoSergAlex</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:NikoSergAlex&amp;diff=17563"/>
		<updated>2016-11-16T10:30:28Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* отсутствует описание вклада каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:53, 26 октября 2016 (MSK)&lt;br /&gt;
** +--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:52, 2 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
* п.п. 1.7 не соответствует рисунку информационного графа--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:53, 26 октября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
* п.п. 1.8 и 1.9 не раскрыт--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:53, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:30, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
===Пример правильно оформленной работы===&lt;br /&gt;
&lt;br /&gt;
* http://algowiki-project.org/ru/Разложение_Холецкого_(метод_квадратного_корня)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Второй этап проверки ==&lt;br /&gt;
&lt;br /&gt;
=== Итерация 1 ===&lt;br /&gt;
&lt;br /&gt;
Начальные замечания и вопросы (возможно будут появляться новые, пишите, пожалуйста, комментарии по ходу исправлений и ответы):&lt;br /&gt;
&lt;br /&gt;
* Исправить ошибки в формулах в разделе 1.2&lt;br /&gt;
* Как решение векового уравнения относится к макроструктуре алгоритма (раздел 1.4)&lt;br /&gt;
* Исправлялся ли информационный граф? То, что имеется сейчас не соответствует инструкции (https://algowiki-project.org/ru/%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82_%D0%B2%D0%B8%D0%B7%D1%83%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D0%93%D0%90) &lt;br /&gt;
Граф нужно кардинально переработать.&lt;br /&gt;
* Раздел 1.8 необзодимо написать намного более подробно.&lt;br /&gt;
* Откуда получены значения параллельной сложности алгоритма в разделе 1.10?&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=17562</id>
		<title>Участник:Vika3410/Метод Разделяй и властвуй</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=17562"/>
		<updated>2016-11-16T10:29:34Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Авторы статьи: [[Участник:NikoSergAlex|Сергеев Никита]] (группа 614) (1.1, 1.2, 1.5, 1.6, 1.7, 1.8, 1.10), [[Участница:Vika3410|Стрельцова Виктория]] (группа 614) (1.2, 1.3, 1.4, 1.6, 1.7, 1.9, 1.10)&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Метод &amp;quot;Разделяй и властвуй&amp;quot; вычисления собственных значений и векторов трёхдиагональной матрицы&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt; c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
| parallel_complexity = &amp;lt;math&amp;gt; O(n^{2.3}) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
крайне редко: &amp;lt;math&amp;gt;  O(n^{2}) &amp;lt;/math&amp;gt; &lt;br /&gt;
| pf_width          = &lt;br /&gt;
| input_data        =  &amp;lt;math&amp;gt; 2n - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       =  &amp;lt;math&amp;gt; n + n^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - является наиболее быстрым из существующих методов вычисления всех собственных значений и собственных векторов трехдиагональной матрицы, начиная с порядка n, который примерно равен 26. (Точное значение этого порогового порядка в конкретном случае зависит от компьютера.) &lt;br /&gt;
&lt;br /&gt;
Численно устойчивая реализация данного метода весьма не тривиальна, так как не смотря на то, что впервые метод был предложен в 1981 г., &amp;quot;правильный&amp;quot; способ реализации метода был найден лишь в 1992 г. Этот способ был воплощен при помощи LAPACK-программ ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В этих программах стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка выше 25. &lt;br /&gt;
&lt;br /&gt;
Для матриц порядка ниже 25 (или в тех случаях, когда требуются только собственные значения) автоматически происходит переход к методу QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В предположении, что известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt;T_{i}=Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. На самом деле, они будут вычисляться рекурсивно тем же самым алгоритмом. &lt;br /&gt;
&lt;br /&gt;
Можно установить связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Получается:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} L_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt; u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v &amp;lt;/math&amp;gt; - матрица, в первой строке которой находятся элементы последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt;, а во второй - элементы первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;, так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;.&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет такие же собственные значения, что и подобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} L_{1} &amp;amp; 0 \\ 0 &amp;amp; L_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
В дальнейшем предполагаем, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по не возрастанию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, потребуется вычислить её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Так как &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденная, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - является собственным значением. Можем заметить, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной путём добавления матрицы ранга 1. Определитель такой матрицы не сложно вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 1.&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Следовательно, получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; являются корнями так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны между собой и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа, который показан на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 1.jpg|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
По рисунку можно заметить, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для данного графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Так как &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Из этого следует, что корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень будет находиться слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, которая является монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который будет быстро и монотонно сходиться к каждому из корней, при условии, что начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать тот факт, что на практике метод сходится к каждому собственному значению за строго ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, следовательно для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 2.&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = &lt;br /&gt;
&lt;br /&gt;
(D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u = &lt;br /&gt;
&lt;br /&gt;
u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u]= &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = u + \alpha(D - \alpha I)^{-1}u - u = \alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
поскольку &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Что и требовалось доказать.&lt;br /&gt;
&lt;br /&gt;
Получается, что для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, данная формула не обеспечивает нам численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Учёным потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Подробнее детали будут обсуждаться ниже в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это будет не так, то вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а следовательно &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: в случае, если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;&#039;дефляции&#039;&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, в том случает, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, следовательно при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; применяя левое умножение &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не будет требовать никаких затрат. Аналогичное упрощение возможно в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, которая связана с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения будет являться вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов при помощи умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt;. Эта операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём будет говориться ниже, в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
&lt;br /&gt;
Данной операции предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
В разделе [[#Информационный граф]] описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
Далее пойдет речь о решении &#039;&#039;&#039;векового уравнения&#039;&#039;&#039;, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 2. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.2 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 2.jpg|thumb|center|800px|Рис. 2. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
&amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (*)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (*) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt; удовлетворяла условиям&lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} \equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Рассмотрим каким именно образом происходит вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы при помощи стратегии &amp;quot;разделяй и властвуй&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В случае, &#039;&#039;&#039;если&#039;&#039;&#039; &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
1.   Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотрим сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов, которое потребуется при обработке матрицы размера &amp;lt;math&amp;gt; n * &lt;br /&gt;
n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. В таком случае &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматривать как плотные матрицы и использовать стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке будет равна 1. Следовательно, именно это умножение и будет составлять наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решив это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно оказывается гораздо меньше 1, так как матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, которое называется дефляцией, и которое было описано выше.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
На рисунке 3 изображена структура алгоритма.&lt;br /&gt;
[[File:Сергеев 5.jpg|thumb|center|left|1000px|Рис. 3. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Данная структура алгоритма позволяет выполнять рекурсивный вызов функций для двух подматриц исходной матрицы как последовательно, так и параллельно. &lt;br /&gt;
При последовательной реализации алгоритма вызов функций производится по очереди - сначала для одной подматрицы, затем для другой. При параллельной реализации алгоритма данные функции могут отрабатывать одновременно.&lt;br /&gt;
Поскольку функции являются рекурсивными, их параллельный вызов даст значительный выигрыш во времени.&lt;br /&gt;
При размерности матрицы больше 3 параллельная реализация позволяет сократить количество этапов работы алгоритма больше, чем в два раза.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: трёхдиагональная симметрическая матрица, описанная в разделе [[#Математическое описание алгоритма]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt; 2n - 1 &amp;lt;/math&amp;gt; (в силу симметричности достаточно хранить только диагональ и над/поддиагональные элементы).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: матрица собственных значений размера &amp;lt;math&amp;gt; 1 * n &amp;lt;/math&amp;gt; и матрица собственных векторов размера &amp;lt;math&amp;gt; n * n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt; n + n^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Данный алгоритм является самым быстрым алгоритмом среди существующих: Разделяй и властвуй, QR, Бисекция и обратная итерация.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Последовательная сложность данного алгоритма:&#039;&#039;&#039; &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Параллельная сложность алгоритма (за счет дефляции):&#039;&#039;&#039; &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;;  в крайне редких случаях: &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Особенности алгоритма:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
1. Дефляция ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Адаптированный метод Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
TBD&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Единственный найденный пример реализации описан в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot; ]   ([3] с. 7, 19-20).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Дж. Деммель, &amp;quot;Вычислительная линейная алгебра&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
[3] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9&amp;diff=17557</id>
		<title>Обсуждение участника:Роман Землянский</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9&amp;diff=17557"/>
		<updated>2016-11-16T10:27:16Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* п. 1.1 &amp;quot;...собственные векторы трехдиагональной матриц,&amp;quot; заменить на &amp;quot;...собственные векторы трехдиагональной матрицы,&amp;quot;--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:48, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 1.7 приведенный рисунок не является рисунком информационного графа--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:48, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 1.8, 1.9 оценка не произведена--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:48, 26 октября 2016 (MSK)&lt;br /&gt;
* п. 2.4 не заполнен--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 13:26, 16 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Второй этап проверки ==&lt;br /&gt;
&lt;br /&gt;
=== Итерация 1 ===&lt;br /&gt;
&lt;br /&gt;
Начальные замечания и комментарии (возможно будут появляться новые, пишите, пожалуйста, комментарии по ходу исправлений и ответы):&lt;br /&gt;
&lt;br /&gt;
* Исправить ошибки в формулах в разделе 1.2&lt;br /&gt;
* Исправить ошибку в разделе 1.9&lt;br /&gt;
* Раздел 2.7 - как это не найдены реализации, если в пункте 1.1 они указаны?&lt;br /&gt;
* По информационному графу не видно, что дерево может быть большей высоты, нужно бы это как-то отразить. И не совсем очевидно, как Divide переходит в Conquer. Нужно постараться, чтобы граф был более информативен и без чтения остальных разделов.&lt;/div&gt;</summary>
		<author><name>IgorS</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=16046</id>
		<title>Участник:Timokhinivan/Быстрое преобразование Фурье</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:Timokhinivan/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=16046"/>
		<updated>2016-11-07T12:13:32Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Быстрое преобразование Фурье&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Автор описания: [[U:Timokhinivan|Тимохин И.В.]], ВМК МГУ.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&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;O(n \log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Преобразование Фурье задаётся следующей формулой:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y_k = \sum_{l = 0}^{N-1} X_l \epsilon^{kl}_N,\qquad k = \overline{0,N-1}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;\epsilon_N = e^{-\frac{2i\pi}{N}}&amp;lt;/math&amp;gt; — первообразный корень&lt;br /&gt;
из 1 степени &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, преобразование Фурье является линейным и задаётся&lt;br /&gt;
матрицей &amp;lt;math&amp;gt;F = \left\{ \epsilon^{kl}_N \right\}_{k\,l = 0}^{N-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Многомерное преобразование Фурье ====&lt;br /&gt;
Естественным обобщением описанного одномерного преобразования Фурье является многомерный вариант:&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Y(l_1,\ldots,l_d) = \sum_{k_1 = 0}^{N_1-1} \cdots \sum_{k_d = 0}^{N_d-1} X(k_1,\ldots,k_d) \prod_{j=1}^{d} \epsilon_{N_j}^{l_j k_j}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Многомерное преобразование Фурье эквивалентно последовательному выполнению одномерных преобразований по каждому измерению; при использовании для этого рекурсивного алгоритма, общая сложность вычислений также составляет &amp;lt;math&amp;gt;O(N\log N)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;N = \prod_{j=1}^d N_j&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Сведение к (почти) двумерному преобразованию (алгоритм Кули-Тьюки) ====&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; — составное, т.е. &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt;, то возможно&lt;br /&gt;
существенно сократить вычислительные расходы за счёт сведения к&lt;br /&gt;
двумерному преобразованию Фурье.  А именно, положим &amp;lt;math&amp;gt;X(l_1, l_2)&lt;br /&gt;
= X_{l_1 n + l_2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt; Y(k_1, k_2) = Y_{k_1 m + k_2}&amp;lt;/math&amp;gt;,&lt;br /&gt;
где &amp;lt;math&amp;gt;l_1, k_2 = \overline{0, m-1}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;l_2, k_1 =&lt;br /&gt;
\overline{0, n-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В этом случае&amp;lt;ref name=&amp;quot;bachv&amp;quot;&amp;gt;Н.С. Бахвалов, Н.П. Жидков, Г.М. Кобельков, &#039;&#039;Численные методы&#039;&#039;, 2003 г., ISBN: 5-94774-060-5&amp;lt;/ref&amp;gt;&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  \begin{align}&lt;br /&gt;
    Y(k_1, k_2) &amp;amp;=&amp;amp; \sum_{l_2=0}^{n-1} (\epsilon^{k_2 l_2}_N \hat{X}(k_2, l_2)) \epsilon^{k_1 l_2}_n \\&lt;br /&gt;
    \hat{X}(k_2, l_2) &amp;amp;=&amp;amp; \sum_{l_1=0}^{m-1} X(l_1, l_2) \epsilon^{k_2 l_1}_m&lt;br /&gt;
  \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это даёт следующий алгоритм вычисления преобразования:&lt;br /&gt;
&lt;br /&gt;
# Записываем исходный вектор в матрицу &amp;lt;math&amp;gt;m\times n&amp;lt;/math&amp;gt; по строкам.&lt;br /&gt;
# Применяем к каждому столбцу преобразование Фурье.&lt;br /&gt;
# Умножаем элемент в позиции &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;\epsilon^{ij}_N&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Применяем к каждой строке преобразование Фурье.&lt;br /&gt;
# Результат записан в получившейся матрице по столбцам.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм уже даёт существенный выигрыш по сравнению с обычным&lt;br /&gt;
умножением матрицы на вектор: &amp;lt;math&amp;gt; O(m^2 n + n^2 m) &amp;lt;/math&amp;gt; против&lt;br /&gt;
&amp;lt;math&amp;gt;O(m^2 n^2)&amp;lt;/math&amp;gt;.  Однако наилучших результатов можно добиться,&lt;br /&gt;
если применять этот алгоритм рекурсивно на этапах 2 и 4.&lt;br /&gt;
&lt;br /&gt;
Так, если &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;, то целесообразно&lt;br /&gt;
на каждом уровне рекурсии «отщеплять» одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt;.  В этом&lt;br /&gt;
случае задача сводится к вычислению &amp;lt;math&amp;gt; \prod_{i\neq j} p_i &amp;lt;/math&amp;gt;&lt;br /&gt;
преобразований Фурье порядка &amp;lt;math&amp;gt;p_j&amp;lt;/math&amp;gt; для всех&lt;br /&gt;
&amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;.  При небольших &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; (например, 2), это&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; Y_0 = \sum_{l=0}^{N-1} X_l&amp;lt;/math&amp;gt; вычисляется непосредственно.&lt;br /&gt;
# Любым способом вычисляется &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; — первообразный корень мультипликативной группы поля &amp;lt;math&amp;gt;\mathbb{Z}_{N}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# &amp;lt;math&amp;gt; Y_{g^{N-p} \bmod N} = X_0 + \sum_{q=0}^{N-2} X_{g^q \bmod N} \epsilon_N^{g^{q-p}\bmod N} &amp;lt;/math&amp;gt;.  &lt;br /&gt;
&lt;br /&gt;
Последнее выражение представляет собой свёртку и вычисляется с помощью всё того же преобразования Фурье.  В принципе, это можно проделывать непосредственно, поскольку порядок свёртки &amp;lt;math&amp;gt;N - 1&amp;lt;/math&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;
&amp;lt;math&amp;gt;O(mn (\log m + \log n))&amp;lt;/math&amp;gt; операций против&lt;br /&gt;
&amp;lt;math&amp;gt;O(mn)&amp;lt;/math&amp;gt; для умножения на поправочные коэффициенты (шаг 3).&lt;br /&gt;
&lt;br /&gt;
То же верно и для алгоритма в целом в случае &amp;lt;math&amp;gt;N = \prod_{i=1}^{K} p_i&amp;lt;/math&amp;gt;;&lt;br /&gt;
а именно, суммарно наибольшие вычислительные затраты связаны с&lt;br /&gt;
вычислением преобразований Фурье порядка &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;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;n&amp;lt;/math&amp;gt; рекурсивных вызовов алгоритма.&lt;br /&gt;
# Умножение всех элементов рабочего вектора на поправочные коэффициенты.&lt;br /&gt;
# Ещё &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рекурсивных вызовов.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Псевдокод (для метода Кули-Тьюки):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;fft(X, N)&lt;br /&gt;
begin&lt;br /&gt;
  (m, n) = factor(N)&lt;br /&gt;
&lt;br /&gt;
  if (n == 1) // Предполагается, что n &amp;lt;= m&lt;br /&gt;
    direct_fft(X, N) // Через умножение на матрицу&lt;br /&gt;
&lt;br /&gt;
  // 1&lt;br /&gt;
  transpose(X, m, n) //Транспонирует X как матрицу m x n, хранящуюся по строкам&lt;br /&gt;
   &lt;br /&gt;
  // 2&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    fft(X(i * m : (i + 1) * m - 1), m)&lt;br /&gt;
&lt;br /&gt;
  // 3&lt;br /&gt;
  do i = 0, n - 1&lt;br /&gt;
    do j = 0, m - 1&lt;br /&gt;
      X(i * m + j) *= eps(i * j, N)&lt;br /&gt;
&lt;br /&gt;
  // 4&lt;br /&gt;
  transpose(X, n, m)&lt;br /&gt;
&lt;br /&gt;
  // 5&lt;br /&gt;
  do j = 0, m - 1&lt;br /&gt;
    fft(X(j * n : (j + 1) * n - 1), n)&lt;br /&gt;
&lt;br /&gt;
  // 6&lt;br /&gt;
  transpose(X, m, n)&lt;br /&gt;
end&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь &amp;lt;code&amp;gt;eps&amp;lt;/code&amp;gt; может как вычислять соответствующие коэффициенты, так и брать их из заранее заготовленной таблицы.  Последний способ требует увеличения затрат по памяти, но позволяет существенно сэкономить на вычислениях, а потому является предпочтительным.&lt;br /&gt;
&lt;br /&gt;
Кроме того, шаги 3 и 4 можно переставлять произвольным образом (с очевидными изменениями в шаге 3).&lt;br /&gt;
&lt;br /&gt;
Особо следует отметить, что если порядок элементов в выходном массиве не важен, шаг 6 можно пропустить.  &lt;br /&gt;
&lt;br /&gt;
Так, например, если преобразование Фурье используется для вычисления свёртки векторов, то конкретный порядок элементов в их образах Фурье совершенно не важен, лишь бы он был одинаков для обоих векторов.  В этом случае в прямом преобразовании можно опустить шаг 6, а в обратном — 1.&lt;br /&gt;
&lt;br /&gt;
Если необходимо учитывать случай большого простого &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; (что может и не требоваться, в зависимости от задачи), то в первом условном операторе следует дополнительно проверить величину &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; и, если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; большое, переключиться на алгоритм Радера.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Непосредственно из описания алгоритма получаем, что если сложность&lt;br /&gt;
вычисления преобразования Фурье обозначить &amp;lt;math&amp;gt;f_N&amp;lt;/math&amp;gt;, то&lt;br /&gt;
&amp;lt;math&amp;gt;f_{mn} = n f_{m} + m f_{n} + mn&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N = \prod_{i=1}^K p_i&amp;lt;/math&amp;gt;, и на каждом шаге&lt;br /&gt;
«отщеплять» от него одно &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt;, а преобразование Фурье для&lt;br /&gt;
&amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; вычислять «в лоб», то общее количество операций&lt;br /&gt;
составит &amp;lt;math&amp;gt;O\left(N\sum_{i=1}^K p_i\right)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В частности, если &amp;lt;math&amp;gt;p_i \leq P&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;K \leq \log_P N&amp;lt;/math&amp;gt;&lt;br /&gt;
и для сложности получаем &amp;lt;math&amp;gt;O(NP\log_P N)&amp;lt;/math&amp;gt;.  Полагая&lt;br /&gt;
&amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; фиксированным, получаем заявленную сложность &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
[[Файл:FourierRecursiveGraph.svg|center|thumb|600px|&#039;&#039;&#039;Рисунок 1.&#039;&#039;&#039; Информационный граф алгоритма Кули-Тьюки для &amp;lt;math&amp;gt;N = 15&amp;lt;/math&amp;gt;. Исходные данные обозначены фиолетовым, результат — красным.  Преобразования Фурье для &amp;lt;math&amp;gt;m = 5&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n = 3&amp;lt;/math&amp;gt; представлены как «чёрные ящики». Умножение на поворотные коэффициенты представлено оранжевыми узлами.]]&lt;br /&gt;
&lt;br /&gt;
Каждый уровень рекурсивного вызова состоит из трёх этапов: рекурсивный вызов по столбцам, умножение на поправочные коэффициенты и рекурсивного вызова по строкам. Таким образом для высоты ЯПФ имеем&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 h(mn) = 1 + h(m) + h(n)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для «элементарных» БПФ высота ЯПФ будет та же, что и для [[Умножение плотной матрицы на вектор|умножения матрицы на вектор]].&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учётом разложения &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;h(N) = O\left(\sum_{i=1}^K p_i\right) = O(P \log_P N) = O(\log N)&amp;lt;/math&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;
&amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;n&amp;lt;/math&amp;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;N&amp;lt;/math&amp;gt;.  Однако в частных случаях, возможны более компактные представления для параметров и результатов.&lt;br /&gt;
&lt;br /&gt;
Для описания этих случаев нам понадобится матрица&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
  Q = \begin{bmatrix} &lt;br /&gt;
     1 &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;   \\ &lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp; 1 \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp;        &amp;amp; 1 &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp;   &amp;amp; \cdots &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp;   &amp;amp; 1 &amp;amp;        &amp;amp;   &amp;amp;   \\&lt;br /&gt;
       &amp;amp; 1 &amp;amp;   &amp;amp;        &amp;amp;   &amp;amp;&lt;br /&gt;
 \end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — вещественный, то &amp;lt;math&amp;gt;Y = Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; — чисто мнимый, то &amp;lt;math&amp;gt;Y = -Q\bar{Y}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — вещественный.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;X = -Q\bar{X}&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; — чисто мнимый.&lt;br /&gt;
&lt;br /&gt;
В каждом случае, &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Y&amp;lt;/math&amp;gt; требуют для своего хранения в два раза меньше памяти, чем в общем случае.&lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии дополнительной информации о входном векторе, возможно сократить объём памяти используемый для хранения входных и выходных данных.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
Обозначая матрицу преобразования &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, имеем&amp;lt;ref name=&amp;quot;voevodtyrt&amp;quot;&amp;gt;В.В. Воеводин, Е.Е. Тыртышников, &#039;&#039;Вычислительные процессы с теплицевыми матрицами&#039;&#039;, Москва, «Наука», 1987 г.&amp;lt;/ref&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;math&amp;gt;F = F^T&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^{-1} = \tfrac{1}{N} F^*&amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt;F^* = QF &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Свойство 2 представляет особый интерес, поскольку позволяет вычислять обратное преобразование Фурье по тем же алгоритмам, что и прямое, поскольку &amp;lt;math&amp;gt;F^*&amp;lt;/math&amp;gt; представляет собой матрицу того же вида, что и &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, но с &amp;lt;math&amp;gt;\bar{\epsilon}&amp;lt;/math&amp;gt; вместо &amp;lt;math&amp;gt;\epsilon&amp;lt;/math&amp;gt;.  В сущности, свойство 3 позволяет свести задачу к исходной непосредственно, но ценой дополнительных пересылок.&lt;br /&gt;
&lt;br /&gt;
Кроме того, из второго свойства следует&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
 \|F x\|_2 = \sqrt{N} \|x\|_2&lt;br /&gt;
&amp;lt;/math&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;\epsilon^{l}_{n} = \epsilon^{lm}_{mn} = \epsilon^{lm}_{N}&amp;lt;/math&amp;gt;, все необходимые рекурсивному вызову значения поворотных коэффициентов уже имеются в используемом в текущем вызове массиве, а потому его достаточно подготовить один раз на весь алгоритм.&lt;br /&gt;
&lt;br /&gt;
==== Нерекурсивный случай ====&lt;br /&gt;
&lt;br /&gt;
На нижнем уровне рекурсии преобразование Фурье вычисляется путём умножения на матрицу.  Поскольку этот алгоритм, вообще говоря, более пригоден для векторизации, чем рекурсивная реализация, имеет смысл не разбивать &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; на &#039;&#039;совсем&#039;&#039; мелкие множители (&amp;lt;math&amp;gt;p_i \equiv 2&amp;lt;/math&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;
&#039;&#039;Многие&#039;&#039; используемые в алгоритме данные зависят только от порядка преобразования — массив поворотных коэффициентов, факторизация &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;g&amp;lt;/math&amp;gt; в алгоритме Радера, конкретная задаваемая им перестановка и оптимальный порядок свёртки там же.  Вычисление некоторых из них сопоставимо по сложности со всем остальным алгоритмом.  Имея это ввиду, если планируется вычисление нескольких преобразований Фурье одного порядка, имеет смысл подготовить все эти данные заранее.&lt;br /&gt;
&lt;br /&gt;
==== Численная устойчивость ====&lt;br /&gt;
&lt;br /&gt;
Поскольку все преобразования в алгоритме являются, с точностью до константы &amp;lt;math&amp;gt;\leq \sqrt{N}&amp;lt;/math&amp;gt;, унитарными, катастрофического роста ошибок округления не предвидится, а потому все вычисления возможно проводить с целевой точностью.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
[[Файл:IT_FFTMemaccess.png|center|thumb|800px|Рисунок 2. Профиль обращений в память простого алгоритма Кули-Тьюки с &amp;lt;math&amp;gt;p = (2, 2, 3, 5, 2)&amp;lt;/math&amp;gt;. Сверху вниз: вспомогательная память для прямого умножения матрицы на вектор, память для хранения поворотных коэффициентов, рабочий вектор. Реализация не использует транспонирования векторов в явном виде.]]&lt;br /&gt;
&lt;br /&gt;
Как видно, алгоритм в целом обладает невысокой локальностью, что связано с его структурой (множество рекурсивных шагов, каждый из которых охватывает существенную часть рабочего вектора) и организацией доступа к поворотным коэффициентам. Некоторого улучшения локальности можно добиться, если транспонировать рабочую «матрицу» в памяти, но это либо требует дополнительной памяти, либо, если транспонировать «на месте», существенно усложняет алгоритм.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В случае составного &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; с &#039;&#039;достаточно&#039;&#039; большими делителями, наиболее очевидным и простым вариантом является распределение по процессорам рекурсивных вызовов алгоритма Кули-Тьюки. В этом случае на весь алгоритм приходится только три пересылки, но зато они проходят по схеме «все-со-всеми» (транспонирование матрицы) и не могут выполняться параллельно с вычислениями.&lt;br /&gt;
&lt;br /&gt;
Отдельно заметим, что, как уже упоминалось, первой либо последней группы пересылок можно избежать, если порядок элементов результата не важен.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; простое либо один из множителей в &amp;lt;math&amp;gt;N = mn&amp;lt;/math&amp;gt; всегда мал, ситуация усложняется. В этом случае придётся применять алгоритм Радера на межпроцессорном уровне, что связано с более сложной схемой пересылок и большим их количеством (т.к. приходится осуществлять преобразование Фурье дважды: прямое и обратное). &lt;br /&gt;
&lt;br /&gt;
В связи с этим, некоторые существующие реализации (например, Intel® MKL) просто не поддерживают этот случай. Впрочем, некоторые реализации не поддерживают большие простые делители &#039;&#039;вовсе&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
=== Динамические характеристики и особенности реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм быстрого преобразования Фурье относится к базовым алгоритмам вычислительной математики, а потому его реализации существуют во многих библиотеках.  Перечислим некоторые из них:&lt;br /&gt;
&lt;br /&gt;
* [//software.intel.com/en-us/intel-mkl Intel® Math Kernel Library] — содержит в том числе и версию для распределённых систем.  Имеет интерфейсы для C, C++ и Fortran.&lt;br /&gt;
* [http://www.fftw.org FFTW] — содержит распределённую реализацию начиная с версии 3.3.  Имеет интерфейсы для C и Fortran.&lt;br /&gt;
* [//www.gnu.org/software/gsl GNU Scientific Library] — имеет интерфейс для C.&lt;br /&gt;
* [//developer.nvidia.com/cufft cuFFT] — реализация алгоритма для GPU от NVIDIA.&lt;br /&gt;
* [http://gpuopen.com/compute-product/clfft clFFT] — реализация алгоритма на OpenCL для CPU и GPU.  Поддерживает только &amp;lt;math&amp;gt;N = 2^p 3^q 5^r 7^s&amp;lt;/math&amp;gt;&lt;br /&gt;
* [http://www.ibm.com/systems/power/software/essl IBM® Engineering and Scientific Subroutine Library] — для процессоров IBM® POWER™, имеет интерфейсы для C, C++ и Fortran, существует версия для многопроцессорных систем.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=15896</id>
		<title>Участник:Vika3410/Метод Разделяй и властвуй</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=15896"/>
		<updated>2016-11-03T13:30:43Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
Авторы статьи: [[Участник:NikoSergAlex|Сергеев Никита]] (группа 614) (1.1, 1.2, 1.5, 1.6, 1.7, 1.8, 1.10), [[Участница:Vika3410|Стрельцова Виктория]] (группа 614) (1.2, 1.3, 1.4, 1.6, 1.7, 1.9, 1.10)&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Метод &amp;quot;Разделяй и властвуй&amp;quot; вычисления собственных значений и векторов трёхдиагональной матрицы&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt; c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
| parallel_complexity = &amp;lt;math&amp;gt; O(n^{2.3}) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
крайне редко: &amp;lt;math&amp;gt;  O(n^{2}) &amp;lt;/math&amp;gt; &lt;br /&gt;
| pf_width          = &lt;br /&gt;
| input_data        =  &amp;lt;math&amp;gt; 2n - 1 &amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       =  &amp;lt;math&amp;gt; n + n^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - является наиболее быстрым из существующих методов вычисления всех собственных значений и собственных векторов трехдиагональной матрицы, начиная с порядка n, который примерно равен 26. (Точное значение этого порогового порядка в конкретном случае зависит от компьютера.) &lt;br /&gt;
&lt;br /&gt;
Численно устойчивая реализация данного метода весьма не тривиальна, так как не смотря на то, что впервые метод был предложен в 1981 г., &amp;quot;правильный&amp;quot; способ реализации метода был найден лишь в 1992 г. Этот способ был воплощен при помощи LAPACK-программ ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В этих программах стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка выше 25. &lt;br /&gt;
&lt;br /&gt;
Для матриц порядка ниже 25 (или в тех случаях, когда требуются только собственные значения) автоматически происходит переход к методу QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В предположении, что известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt;T_{i}=Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. На самом деле, они будут вычисляться рекурсивно тем же самым алгоритмом. &lt;br /&gt;
&lt;br /&gt;
Можно установить связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Получается:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} L_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt; u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v &amp;lt;/math&amp;gt; - матрица, в первой строке которой находятся элементы последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt;, а во второй - элементы первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;, так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;.&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет такие же собственные значения, что и подобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} L_{1} &amp;amp; 0 \\ 0 &amp;amp; L_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
В дальнейшем предполагаем, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по не возрастанию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, потребуется вычислить её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Так как &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденная, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - является собственным значением. Можем заметить, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной путём добавления матрицы ранга 1. Определитель такой матрицы не сложно вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 1.&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Следовательно, получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; являются корнями так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны между собой и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа, который показан на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 1.jpg|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
По рисунку можно заметить, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для данного графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Так как &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Из этого следует, что корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень будет находиться слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, которая является монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который будет быстро и монотонно сходиться к каждому из корней, при условии, что начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать тот факт, что на практике метод сходится к каждому собственному значению за строго ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, следовательно для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 2.&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = &lt;br /&gt;
&lt;br /&gt;
(D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u = &lt;br /&gt;
&lt;br /&gt;
u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u]= &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = u + \alpha(D - \alpha I)^{-1}u - u = \alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
поскольку &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Что и требовалось доказать.&lt;br /&gt;
&lt;br /&gt;
Получается, что для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, данная формула не обеспечивает нам численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Учёным потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Подробнее детали будут обсуждаться ниже в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это будет не так, то вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а следовательно &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: в случае, если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;&#039;дефляции&#039;&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, в том случает, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, следовательно при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; применяя левое умножение &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не будет требовать никаких затрат. Аналогичное упрощение возможно в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, которая связана с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения будет являться вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов при помощи умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt;. Эта операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём будет говориться ниже, в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
&lt;br /&gt;
Данной операции предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
В разделе [[#Информационный граф]] описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
Далее пойдет речь о решении &#039;&#039;&#039;векового уравнения&#039;&#039;&#039;, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 2. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.2 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 2.jpg|thumb|center|800px|Рис. 2. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
&amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (*)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (*) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt; удовлетворяла условиям&lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} \equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Рассмотрим каким именно образом происходит вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы при помощи стратегии &amp;quot;разделяй и властвуй&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В случае, &#039;&#039;&#039;если&#039;&#039;&#039; &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
1.   Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотрим сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов, которое потребуется при обработке матрицы размера &amp;lt;math&amp;gt; n * &lt;br /&gt;
n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. В таком случае &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматривать как плотные матрицы и использовать стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке будет равна 1. Следовательно, именно это умножение и будет составлять наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решив это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно оказывается гораздо меньше 1, так как матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, которое называется дефляцией, и которое было описано выше.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
На рисунке 3 изображена структура алгоритма.&lt;br /&gt;
[[File:Сергеев 5.jpg|thumb|center|left|1000px|Рис. 3. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Данная структура алгоритма позволяет выполнять рекурсивный вызов функций для двух подматриц исходной матрицы как последовательно, так и параллельно. &lt;br /&gt;
При последовательной реализации алгоритма вызов функций производится по очереди - сначала для одной подматрицы, затем для другой. При параллельной реализации алгоритма данные функции могут отрабатывать одновременно.&lt;br /&gt;
Поскольку функции являются рекурсивными, их параллельный вызов даст значительный выигрыш во времени.&lt;br /&gt;
При размерности матрицы больше 3 параллельная реализация позволяет сократить количество этапов работы алгоритма больше, чем в два раза.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: трёхдиагональная симметрическая матрица, описанная в разделе [[#Математическое описание алгоритма]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt; 2n - 1 &amp;lt;/math&amp;gt; (в силу симметричности достаточно хранить только диагональ и над/поддиагональные элементы).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: матрица собственных значений размера &amp;lt;math&amp;gt; 1 * n &amp;lt;/math&amp;gt; и матрица собственных векторов размера &amp;lt;math&amp;gt; n * n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt; n + n^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Данный алгоритм является самым быстрым алгоритмом среди существующих: Разделяй и властвуй, QR, Бисекция и обратная итерация.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Последовательная сложность данного алгоритма:&#039;&#039;&#039; &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Параллельная сложность алгоритма (за счет дефляции):&#039;&#039;&#039; &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;;  в крайне редких случаях: &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Особенности алгоритма:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
1. Дефляция ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Адаптированный метод Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
TBD&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Единственный найденный пример реализации описан в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot; ]   ([3] с. 7, 19-20).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Дж. Деммель, &amp;quot;Вычислительная линейная алгебра&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
[3] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:NikoSergAlex&amp;diff=15771</id>
		<title>Обсуждение участника:NikoSergAlex</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:NikoSergAlex&amp;diff=15771"/>
		<updated>2016-11-02T11:52:28Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Замечания к статье ==&lt;br /&gt;
&lt;br /&gt;
* отсутствует описание вклада каждого автора--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:53, 26 октября 2016 (MSK)&lt;br /&gt;
** +--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:52, 2 ноября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
* п.п. 1.7 не соответствует рисунку информационного графа--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:53, 26 октября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
* п.п. 1.8 и 1.9 не раскрыт--[[Участник:IgorS|IgorS]] ([[Обсуждение участника:IgorS|обсуждение]]) 14:53, 26 октября 2016 (MSK)&lt;br /&gt;
&lt;br /&gt;
===Пример правильно оформленной работы===&lt;br /&gt;
&lt;br /&gt;
* http://algowiki-project.org/ru/Разложение_Холецкого_(метод_квадратного_корня)&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=15765</id>
		<title>Участник:Vika3410/Метод Разделяй и властвуй</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:Vika3410/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9&amp;diff=15765"/>
		<updated>2016-11-02T11:46:02Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Авторы статьи: [[Участник:NikoSergAlex|Сергеев Никита]] (группа 614) (1.1, 1.2, 1.5, 1.6, 1.7, 1.9, 1.10), [[Участница:Vika3410|Стрельцова Виктория]] (группа 614) (1.2, 1.3, 1.4, 1.6, 1.7, 1.8, 1.10)&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Метод &amp;quot;Разделяй и властвуй&amp;quot; вычисления собственных значений и векторов трёхдиагональной матрицы&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt; c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
| parallel_complexity = &amp;lt;math&amp;gt; O(n^{2.3}) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
крайне редко: &amp;lt;math&amp;gt;  O(n^{2}) &amp;lt;/math&amp;gt; &lt;br /&gt;
| pf_width          = &lt;br /&gt;
| input_data        = трёхдиагональная матрица&lt;br /&gt;
| output_data       = матрица собственных значений и матрица собственных векторов исходной матрицы&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - является наиболее быстрым из существующих методов вычисления всех собственных значений и собственных векторов трехдиагональной матрицы, начиная с порядка n, который примерно равен 26. (Точное значение этого порогового порядка в конкретном случае зависит от компьютера.) &lt;br /&gt;
&lt;br /&gt;
Численно устойчивая реализация данного метода весьма не тривиальна, так как не смотря на то, что впервые метод был предложен в 1981 г., &amp;quot;правильный&amp;quot; способ реализации метода был найден лишь в 1992 г. Этот способ был воплощен при помощи LAPACK-программ ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В этих программах стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка выше 25. &lt;br /&gt;
&lt;br /&gt;
Для матриц порядка ниже 25 (или в тех случаях, когда требуются только собственные значения) автоматически происходит переход к методу QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В предположении, что известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt;T_{i}=Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. На самом деле, они будут вычисляться рекурсивно тем же самым алгоритмом. &lt;br /&gt;
&lt;br /&gt;
Можно установить связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Получается:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} L_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt; u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v &amp;lt;/math&amp;gt; - матрица, в первой строке которой находятся элементы последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt;, а во второй - элементы первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;, так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;.&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет такие же собственные значения, что и подобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} L_{1} &amp;amp; 0 \\ 0 &amp;amp; L_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
В дальнейшем предполагаем, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по не возрастанию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, потребуется вычислить её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Так как &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденная, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - является собственным значением. Можем заметить, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной путём добавления матрицы ранга 1. Определитель такой матрицы не сложно вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 1.&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Следовательно, получаем&lt;br /&gt;
&amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; являются корнями так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны между собой и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа, который показан на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 1.jpg|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
По рисунку можно заметить, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для данного графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Так как &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Из этого следует, что корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень будет находиться слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, которая является монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который будет быстро и монотонно сходиться к каждому из корней, при условии, что начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать тот факт, что на практике метод сходится к каждому собственному значению за строго ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, следовательно для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;Лемма 2.&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = &lt;br /&gt;
&lt;br /&gt;
(D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u = &lt;br /&gt;
&lt;br /&gt;
u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u]= &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; = u + \alpha(D - \alpha I)^{-1}u - u = \alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
поскольку &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Что и требовалось доказать.&lt;br /&gt;
&lt;br /&gt;
Получается, что для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов потребуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, данная формула не обеспечивает нам численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Учёным потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Подробнее детали будут обсуждаться ниже в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это будет не так, то вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а следовательно &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: в случае, если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;&#039;дефляции&#039;&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, в том случает, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, следовательно при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; применяя левое умножение &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не будет требовать никаких затрат. Аналогичное упрощение возможно в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, которая связана с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения будет являться вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов при помощи умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt;. Эта операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём будет говориться ниже, в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
&lt;br /&gt;
Данной операции предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
В разделе [[#Информационный граф]] описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
Далее пойдет речь о решении &#039;&#039;&#039;векового уравнения&#039;&#039;&#039;, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 2. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.2 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 2.jpg|thumb|center|800px|Рис. 2. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
&amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (*)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (*) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt; удовлетворяла условиям&lt;br /&gt;
&amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} \equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
Рассмотрим каким именно образом происходит вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы при помощи стратегии &amp;quot;разделяй и властвуй&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В случае, &#039;&#039;&#039;если&#039;&#039;&#039; &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
1.   Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотрим сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов, которое потребуется при обработке матрицы размера &amp;lt;math&amp;gt; n * &lt;br /&gt;
n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. В таком случае &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
         &amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматривать как плотные матрицы и использовать стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке будет равна 1. Следовательно, именно это умножение и будет составлять наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решив это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно оказывается гораздо меньше 1, так как матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, которое называется дефляцией, и которое было описано выше.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
На рисунке 3 изображена структура всего алгоритма, на рисунке 4 подробно описана одна из ячеек структуры.&lt;br /&gt;
[[File:Сергеев 3.jpg|thumb|center|left|800px|Рис. 3. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
[[File:Сергеев 4.jpg|thumb|center|1200px|Рис. 4. Детальное описание одного блока алгоритма]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
На информационном графе алгоритма имеются два параллельных блока вызова рекурсивных функций &#039;&#039;dc_eig&#039;&#039; для вычисления собственных значений и для векторов матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt; - это единственная часть алгоритма, сводящаяся к параллелизму. При реализации алгоритма без параллелизма функции &#039;&#039;dc_eig&#039;&#039; должны отрабатывать последовательно - сначала со входными параметрами &amp;lt;math&amp;gt;T_{1}, Q_{1}, \Lambda_{1}&amp;lt;/math&amp;gt;, затем - &amp;lt;math&amp;gt;T_{2}, Q_{2}, \Lambda_{2}&amp;lt;/math&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;
Данный алгоритм является самым быстрым алгоритмом среди существующих: Разделяй и властвуй, QR, Бисекция и обратная итерация.&lt;br /&gt;
&lt;br /&gt;
  Последовательная сложность данного алгоритма: &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  Параллельная сложность алгоритма (за счет дефляции): &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;;  в крайне редких случаях: &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt; ( [3] стр. 8).&lt;br /&gt;
&lt;br /&gt;
Особенности алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. Дефляция ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Адаптированный метод Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
TBD&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Единственный найденный пример реализации описан в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot; ]   ([3] с. 7, 19-20).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Дж. Деммель, &amp;quot;Вычислительная линейная алгебра&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, &amp;quot;Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
[3] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</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:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9/Zavolskov/Zemlyanskiy&amp;diff=15716</id>
		<title>Участник:Роман Землянский/Zavolskov/Zemlyanskiy</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:%D0%A0%D0%BE%D0%BC%D0%B0%D0%BD_%D0%97%D0%B5%D0%BC%D0%BB%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%D0%B9/Zavolskov/Zemlyanskiy&amp;diff=15716"/>
		<updated>2016-11-02T08:16:42Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt; {{Assignment|IgorS}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Метод «разделяй и властвуй» вычисления собственных значений и векторов симметричной трехдиагональной матрицы&#039;&#039;&#039;&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;
[[#Схема реализации последовательного алгоритма]]&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;
[[#Последовательная сложность алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Ресурс параллелизма алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Входные и выходные данные алгоритма]]&lt;br /&gt;
&lt;br /&gt;
[[#Свойства алгоритма]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
614 группа&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&#039;&#039;&#039;Метод разделяй и властвуй вычисления собственных значений и векторов трёхдиагональной матрицы&#039;&#039;&#039; - это наиболее быстрый из существующих методов, если нужны все собственные значения и собственные векторы трехдиагональной матрицы, начиная с порядка n, примерно равного 26. (Точное значение этого порогового порядка зависит от компьютера.) Его численно устойчивая реализация весьма не тривиальна. В самом деле, хотя впервые метод был предложен еще в 1981 г., &amp;quot;правильный&amp;quot; способ его реализации был найден лишь в 1992 г. Этот способ воплощен LAPACK-программами ssyevd (для плотных матриц) и sstevd (для трехдиагональных матриц). В них стратегия &amp;quot;разделяй-и-влавствуй&amp;quot; используется для матриц порядка, большего чем 25. Для матриц меньшего порядка (или если нужны только собственные значения) происходит автоматический переход к QR-итерации.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Пусть &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
= \begin{bmatrix}&lt;br /&gt;
  a_{1} &amp;amp; b_{1} &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} - b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; a_{m+1} - b_{m} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix} +&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
+ \begin{bmatrix}&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 &amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;b_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&lt;br /&gt;
= \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m} * \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \\ 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix} \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix} &lt;br /&gt;
&lt;br /&gt;
\equiv \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Предположим, что нам известны спектральные разложения матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt; T_{2} &amp;lt;/math&amp;gt;: &lt;br /&gt;
&amp;lt;math&amp;gt; T_{i} = Q_{i} \Lambda_{i} Q_{i}^{T} &amp;lt;/math&amp;gt;. В действительности, они будут рекурсивно вычисляться тем же самым алгоритмом. Установим связь между собственными значениями матрицы Т и собственными значениями матриц&lt;br /&gt;
&amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;. Имеем:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt; &lt;br /&gt;
T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} \Lambda_{1} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2} L_{2} Q_{2}^{T}\end{bmatrix} + b_{m}vv^{T} &lt;br /&gt;
= \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}(\begin{bmatrix} \Lambda_{1} &amp;amp; \\  &amp;amp; \Lambda_{2}\end{bmatrix} + b_{m}vv^{T})\begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}&lt;br /&gt;
  &amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;&lt;br /&gt;
u = \begin{bmatrix} Q_{1}^{T} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}^{T}\end{bmatrix}v&lt;br /&gt;
  &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
так как &amp;lt;math&amp;gt;v = \begin{bmatrix} 0 , \ldots  ,  0 , 1 , 1 , 0 \ldots , 0 \end{bmatrix}^T&amp;lt;/math&amp;gt;, получим матрицу, состоящую из последнего столбца матрицы &amp;lt;math&amp;gt; Q_{1}^{T}&amp;lt;/math&amp;gt; и первого столбца матрицы &amp;lt;math&amp;gt; Q_{2}^{T}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Следовательно, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; имеет те же собственные значения, что и пдобная ей матрица &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, &lt;br /&gt;
где &amp;lt;math&amp;gt;D = \begin{bmatrix} L_{1} &amp;amp; 0 \\ 0 &amp;amp; L_{2}\end{bmatrix}&amp;lt;/math&amp;gt; - диагональная матрица, &lt;br /&gt;
&amp;lt;math&amp;gt;\rho = b_{m}&amp;lt;/math&amp;gt; - число, а &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - вектор. &lt;br /&gt;
&lt;br /&gt;
Будем предполагать, не ограничивая общности, что диагональные элементы &amp;lt;math&amp;gt;d_{1}, \ldots, d_{n}&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; упорядочены по убыванию: &amp;lt;math&amp;gt;d_{n} &amp;lt;= \ldots &amp;lt;=d_{1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Чтобы найти собственные значения матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, вычислим её характеристический многочлен, считая пока матрицу &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденной.&lt;br /&gt;
Тогда&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;det(D + \rho uu^{T} - \lambda I) = det((D - \lambda I)(I + \rho (D- \lambda I)^{-1} uu^{T}))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Поскольку &amp;lt;math&amp;gt;D - \lambda I&amp;lt;/math&amp;gt; невырожденна, &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 0&amp;lt;/math&amp;gt; тогда и только тогда, когда &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - собственное значение. Заметим, что матрица &amp;lt;math&amp;gt;I + \rho (D - \lambda I)^{-1}uu^{T}&amp;lt;/math&amp;gt; получается из единичной добавлением матрицы ранга 1. Определитель такой матрицы легко вычислить.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;&#039;Лемма 1.&#039;&#039;&#039; Справедливо равенство &amp;lt;math&amp;gt;det(I + xy^{T}) = 1 + y^{T}x&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; - векторы.&lt;br /&gt;
&lt;br /&gt;
Таким образом,&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;det(I + \rho (D - \lambda I)^{-1}uu^{T}) = 1 + \rho u^{T}(D - \lambda I)^{-1}u&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt; = 1 + \rho \sum_{i=1, n} \frac{u_{i}^{2}} {d_{i}-\lambda} \equiv f(\lambda)&amp;lt;/math&amp;gt; , &lt;br /&gt;
&lt;br /&gt;
т.е. собственные значения матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; есть корни так называемого векового уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt;. Если все числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i} &amp;lt;&amp;gt; 0&amp;lt;/math&amp;gt; (случай общего положения), то &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет график типа показанного на рис.1(где &amp;lt;math&amp;gt;n = 4&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\rho &amp;gt; 0&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[File:Graphics.PNG|thumb|center|800px|Рис. 1. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{0.5}{1 - \lambda} + \frac{0.5}{2 - \lambda} + \frac{0.5}{3 - \lambda} + \frac{0.5}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Можно видеть, что прямая &amp;lt;math&amp;gt;y = 1&amp;lt;/math&amp;gt; является горизонтальной асимптотой для этого графика, а прямые &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt; есть вертикальные асимптоты. Поскольку &amp;lt;math&amp;gt;f^{&#039;}(\lambda) =  \rho \sum_{i=1, n} \frac{u_{i}^{2}} {(d_{i}-\lambda)^{2}}&amp;gt; 0 &amp;lt;/math&amp;gt;, функция возрастает всюду, кроме точек &amp;lt;math&amp;gt;\lambda = d_{i}&amp;lt;/math&amp;gt;. Поэтому корни функции разделяются числами &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и ещё один корень находится справа от точки &amp;lt;math&amp;gt;d_{1}&amp;lt;/math&amp;gt; (на рис. 1 &amp;lt;math&amp;gt;d_{1} = 4&amp;lt;/math&amp;gt;). (При &amp;lt;math&amp;gt;\rho&amp;lt;0&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; всюду убывает и соответствующий корень находится слева от точки &amp;lt;math&amp;gt;d_{n}&amp;lt;/math&amp;gt;). Для функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, монотонной и гладкой на каждом из интервалов &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;, можно найти вариант метода Ньютона, который быстро и монотонно сходится к каждому из корней, если начальная точка взята в &amp;lt;math&amp;gt;(d_{i+1},d_{i})&amp;lt;/math&amp;gt;. Нам достаточно знать, что на практике метод сходится к каждому собственному значению за ограниченное число шагов. Поскольку вычисление &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;f^{&#039;}(\lambda)&amp;lt;/math&amp;gt; стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов, для вычисления одного собственного значения достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, а для вычисления всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных значений матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; требуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
Для собственных векторов матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt; мы легко можем получить явные выражения.&lt;br /&gt;
&lt;br /&gt;
  &#039;&#039;&#039;Лемма 2.&#039;&#039;&#039; Если &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; - собственное значение матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;, то соответствующий вектор равен &amp;lt;math&amp;gt;(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;. Поскольку матрица &amp;lt;math&amp;gt;D - \alpha I&amp;lt;/math&amp;gt; диагональная, для вычисления такого вектора достаточно &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; флопов.&lt;br /&gt;
&lt;br /&gt;
Доказательство.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;(D + \rho uu^{T})[(D - \alpha I)^{-1}u] = (D - \alpha I + \alpha I + \rho uu^{T})(D - \alpha I)^{-1}u&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=u + \alpha (D - \alpha I)^{-1}u + u[\rho u^{T}(D - \alpha I)^{-1}u] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=u + \alpha(D - \alpha I)^{-1}u - u&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
                                                       поскольку &amp;lt;math&amp;gt; \rho u^{T}(D - \alpha I)^{-1}u + 1 = f(\alpha) = 0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;=\alpha [(D - \alpha I)^{-1}u]&amp;lt;/math&amp;gt;, что и требовалось.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Для вычисления по этой простой формуле всех &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; собственных векторов требуется &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; флопов. К сожалению, формула не обеспечивает численной устойчивости, так как для двух очень близких значений &amp;lt;math&amp;gt;\alpha_{i}&amp;lt;/math&amp;gt; может давать неортогональные приближенные собственные векторы &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;. Потребовалось целое десятилетие для того, чтобы найти устойчивую альтернативу исходному описанию алгоритма. Снова детали будут обсуждаться позднее в данном разделе. &lt;br /&gt;
&lt;br /&gt;
Алгоритм является рекурсивным.&lt;br /&gt;
&lt;br /&gt;
=== Дефляция ===&lt;br /&gt;
&lt;br /&gt;
До сих пор полагалось, что все &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; различны и все &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; отличны от нуля. Если это не так, вековое уравнение &amp;lt;math&amp;gt;f(\lambda)=0&amp;lt;/math&amp;gt; имеет &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; вертикальных асимптот, где &amp;lt;math&amp;gt;k&amp;lt;n&amp;lt;/math&amp;gt;, а потому &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; корней. Однако оказывается, что остальные &amp;lt;math&amp;gt;n - k&amp;lt;/math&amp;gt; собственных значений могут быть определены без каких-либо усилий: если &amp;lt;math&amp;gt;d_{i}=d_{i+1}&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то легко показать, что &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; является собственным значением и для матрицы &amp;lt;math&amp;gt;D + \rho uu^{T}&amp;lt;/math&amp;gt;. В такой ситуации мы говорим о &#039;&#039;дефляции&#039;&#039;. На практике выбирается некоторое пороговое значение и дефляция для числа &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; регистрируется, если в смысле этого порога &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; достаточно близко к &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt; либо &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt; достаточно мало.&lt;br /&gt;
&lt;br /&gt;
Основной выигрыш от использования дефляции состоит не в том, что убыстряется решение векового уравнения - этот этап в любом случае стоит лишь &amp;lt;math&amp;gt;O(n^{2})&amp;lt;/math&amp;gt; операций. Выигрыш заключается в ускорении матричного умножения на последнем шаге алгоритма. Действительно, если &amp;lt;math&amp;gt;u_{i}=0&amp;lt;/math&amp;gt;, то соответствующий собственный вектор есть i-й столбец &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; единичной матрицы. Это означает, что &amp;lt;math&amp;gt;e_{i}&amp;lt;/math&amp;gt; является i-м столбцом в матрице &amp;lt;math&amp;gt;Q_{&#039;}&amp;lt;/math&amp;gt;, поэтому при формировании матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; посредством левого умножения &amp;lt;math&amp;gt;Q_{1}&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;Q_{2}&amp;lt;/math&amp;gt; вычисление i-го столбца не требует никаких затрат. Аналогичное упрощение имеет место в случае &amp;lt;math&amp;gt;d_{i} = d_{i+1}&amp;lt;/math&amp;gt;. При дефляции многих собственных значений устраняется большая часть работы, связанной с матричным умножением.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
Вычислительным ядром последовательной схемы решения является вычисление матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; собственных векторов путём умножения матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}&amp;lt;/math&amp;gt; на матрицу &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; Данная операция имеет сложность &amp;lt;math&amp;gt;cn^{3}&amp;lt;/math&amp;gt; о чём и говорится в разделе [[#Последовательная сложность алгоритма]] .&lt;br /&gt;
Ей предшествует вычисление собственных значений и векторов матрицы &amp;lt;math&amp;gt; D + \rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Ниже описана структура алгоритма, в которой есть блок умножения матриц для вычисления собственных векторов, являющийся вычислительным ядром алгоритма. В соответствующем разделе ([[#Вычислительное ядро алгоритма]]) мы упоминали о том, что данному блоку предшествует вычисление собственных значений, которое производится [https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0 методом Ньютона].&lt;br /&gt;
&lt;br /&gt;
[[File:DivideAndConquerTree.png|thumb|center|left|800px|Рис. 2. Дерево алгоритма &amp;quot;Разделяй и властвуй&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
[[File:InfoGraph.jpg|thumb|center|1200px|Рис. 3. Детальное описание одного блока алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Решение векового уравнения ===&lt;br /&gt;
Подробно стоит поговорить о решении векового уравнения, которое является одной из основных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
Предположим, что некоторое &amp;lt;math&amp;gt;u_{i}&amp;lt;/math&amp;gt;, хотя и мало, все же недостаточно мало для того, чтобы была зарегистрирована дефляция. В этом случае применение метода Ньютона к решению векового уравнения встречается с затруднениями. Вспомним, что пересчет приближённого решения &amp;lt;math&amp;gt;u_{j}&amp;lt;/math&amp;gt; уравнения &amp;lt;math&amp;gt;f(\lambda) = 0&amp;lt;/math&amp;gt; в методе Ньютона основан на следующих положениях:&lt;br /&gt;
&lt;br /&gt;
 1. Вблизи точки &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt; функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; аппроксимируется линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;; график есть прямая линия, касающаяся графика функции &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 2. В качестве &amp;lt;math&amp;gt;\lambda_{j+1}&amp;lt;/math&amp;gt; берётся нуль этого линейного приближения, т.е. &amp;lt;math&amp;gt;l(\lambda_{j+1})=0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Функция, показанная на рис.1, не доставляет видимых трудностей методу Ньютона, поскольку вблизи каждого своего нуля &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; достаточно хорошо аппроксимируется линейными функциями. Однако рассмотрим график функции на рис. 4. Она получена из функции на рис. 1 заменой значения .5 для &amp;lt;math&amp;gt;u_{i}^{2}&amp;lt;/math&amp;gt; на .001. Это новое значение недостаточно мало для того, чтобы вызвать дефляцию. График функции в левой части рис.4 визуально не отличим от её вертикальных и горизонтальных асимптот, поэтому в правой части укрупненно воспроизведён фрагмент графика, прилегающий к вертикальной асимптоте &amp;lt;math&amp;gt;\lambda = 2&amp;lt;/math&amp;gt;. Видно, что график слишком быстро &amp;quot;выполняет поворот&amp;quot; и для большей части значений &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; почти горизонтален. Поэтому, применяя метод Ньютона почти к любому начальному приближению &amp;lt;math&amp;gt;\lambda_{0}&amp;lt;/math&amp;gt;, мы получаем линейное приближение &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; с почти горизонтальным графиком и малым положительным угловым коэффициентом. В результате &amp;lt;math&amp;gt;\lambda_{1}&amp;lt;/math&amp;gt; является отрицательным числом, огромным по абсолютной величине, которое совершенно бесполезно в качестве приближения к истинному корню.&lt;br /&gt;
&lt;br /&gt;
[[File:GraphNewton.png|thumb|center|800px|Рис. 4. График функции &amp;lt;math&amp;gt; f(\lambda) = 1 + \frac{10^{-3}}{1 - \lambda} + \frac{10^{-3}}{2 - \lambda} + \frac{10^{-3}}{3 - \lambda} + \frac{10^{-3}}{4 - \lambda}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Чтобы найти выход из этого положения, можно модифицировать метод Ньютона следующим образом: раз &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; нельзя хорошо приблизить линейной функцией &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt;, попробуем взять в качестве приближения какую-нибудь другую простую функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;. Нет ничего особого именно в прямых линиях: для метода Ньютона вместо &amp;lt;math&amp;gt;l(\lambda)&amp;lt;/math&amp;gt; можно взять любое приближение &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, значения и нули которого легко вычисляются. Функция &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt; имеет полюсы в точках &amp;lt;math&amp;gt;d_{i}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d_{i+1}&amp;lt;/math&amp;gt;, которые определяют её поведение в соответствующих окрестностях. Поэтому при поиске корня в интервале &amp;lt;math&amp;gt;(d_{i+1}, d_{i})&amp;lt;/math&amp;gt; естественно выбрать функцию &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt;, также имеющую эти полюсы, т.е. функцию вида&lt;br /&gt;
 &amp;lt;math&amp;gt;h(\lambda)= \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Константы &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; обеспечивающие, что &amp;lt;math&amp;gt;h(\lambda)&amp;lt;/math&amp;gt; есть приближение к &amp;lt;math&amp;gt;f(\lambda)&amp;lt;/math&amp;gt;, можно выбрать несколькими способами. Отметим, что если &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; уже известны, то уравнение &amp;lt;math&amp;gt;h(\lambda)=0&amp;lt;/math&amp;gt; легко решается относительно &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt;, поскольку сводится к эквивалентному квадратному уравнению&lt;br /&gt;
 &amp;lt;math&amp;gt;c_{1}(d_{i+1}-\lambda)+c_{2}(d_{i}-\lambda)+c_{3}(d_{i}-\lambda)(d_{i+1}-\lambda)=0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt; - приближённое значение корня. определим &amp;lt;math&amp;gt;c_{1},c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_{3}&amp;lt;/math&amp;gt; так, чтобы&lt;br /&gt;
 &amp;lt;math&amp;gt;\frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} + c_{3} = h(\lambda) \approx f(\lambda) = 1 + \rho \sum_{k=1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
для &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в окрестности &amp;lt;math&amp;gt;\lambda_{j}&amp;lt;/math&amp;gt;. Заметим, что&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;f(\lambda) = 1 + \rho \sum_{k=1, i} \frac{u_{k}^{2}} {d_{k}-\lambda} + \rho \sum_{k=i+1, n} \frac{u_{k}^{2}} {d_{k}-\lambda} \equiv 1 + \psi_{1}(\lambda) + \psi_{2}(\lambda)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;\lambda \in (d_{i+1},d_{i})&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt; есть сумма положительных слагаемых, а &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; - сумма отрицательных. Поэтому и &amp;lt;math&amp;gt;\psi_{1}(\lambda)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;\psi_{2}(\lambda)&amp;lt;/math&amp;gt; могут быть вычислены с высокой точностью; однако при их сложении вполне вероятно взаимное уничтожение верных разрядов и потеря относительной точности в сумме. Возьмем числа &amp;lt;math&amp;gt;c_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}&amp;lt;/math&amp;gt;, такие, что функция &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h_{1}(\lambda) \equiv \hat{c_{1}} + \frac{c_{1}}{d_{i}-\lambda}&amp;lt;/math&amp;gt;   удовлетворяет условиям  &amp;lt;math&amp;gt;h_{1}(\lambda_{j}) = \psi_{1}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{1}^{&#039;}(\lambda_{j})=\psi_{1}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (1)&lt;br /&gt;
&lt;br /&gt;
Это означает, что гипербола, являющаяся графиком функции &amp;lt;math&amp;gt;h_{1}(\lambda)&amp;lt;/math&amp;gt;, касается графика функции &amp;lt;math&amp;gt;\psi_{i}(\lambda)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;\lambda = \lambda_{j}&amp;lt;/math&amp;gt;. Два условия в (1) - это обычные условия метода Ньютона, за исключением того, что вместо прямой в качестве приближения используется гипербола. Легко проверить, что &amp;lt;math&amp;gt;c_{1}=\psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})^{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{1}}=\psi_{1}(\lambda_{j}) - \psi_{1}^{&#039;}(\lambda_{j})(d_{i} - \lambda_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Подобным же образом выбираем &amp;lt;math&amp;gt;c_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\hat{c_{2}}&amp;lt;/math&amp;gt; так, чтобы функция &lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h_{2}(\lambda) \equiv \hat{c_{2}} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Удовлетворяла условиям&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;math&amp;gt;h_{2}(\lambda_{j}) = \psi_{2}(\lambda_{j})&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;h_{2}^{&#039;}(\lambda_{j})=\psi_{2}^{&#039;}(\lambda_{j})&amp;lt;/math&amp;gt; (2)&lt;br /&gt;
&lt;br /&gt;
Наконец, полагаем&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;h(\lambda) = 1 + h_{1}(\lambda) + h_{2}(\lambda) = (1 + \hat{c_{1}} + \hat{c_{2}} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;\equiv c_{3} + \frac{c_{1}}{d_{i}-\lambda} + \frac{c_{2}}{d_{i+1}-\lambda}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&#039;&#039;Вычисление собственных значений и собственных векторов симметричной трехдиагональной матрицы посредством стратегии &amp;quot;разделяй и властвуй&amp;quot;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;proc dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T,Q,\Lambda)...&amp;lt;/math&amp;gt; по входной матрице &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; вычисляются выходные матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;, такие, что &amp;lt;math&amp;gt;T = Q\Lambda Q^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Если &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; x &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  1. Присвоить выходным параметрам значения &amp;lt;math&amp;gt; Q = 1, \Lambda = T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Иначе&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
  1. Представить &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в виде &amp;lt;math&amp;gt; T = \begin{bmatrix} T_{1} &amp;amp; 0 \\ 0 &amp;amp; T_{2}\end{bmatrix} + b_{m}vv^{T} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  2. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{1},Q_{1},\Lambda_{1})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  3. &#039;&#039;call dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{2},Q_{2},\Lambda_{2})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  4. Построить &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt; по &amp;lt;math&amp;gt; \Lambda_{1},\Lambda_{2}, Q_{1}, Q_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  5. Найти матрицу собственных значений &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  6. Найти матрицу собственных векторов &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  7. Построить матрицу собственных векторов &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; для матрицы &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt; Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}* Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  8. Присвоить выходным параметрам значения &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\Lambda&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;endif&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Проанализируем сложность алгоритма. Пусть &amp;lt;math&amp;gt;t(n)&amp;lt;/math&amp;gt; - число флопов при обработке матрицы размера &amp;lt;math&amp;gt; n x n&amp;lt;/math&amp;gt; процедурой &#039;&#039;dc_eig&#039;&#039;. Тогда &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; t(n) = 2t(n/2) &amp;lt;/math&amp;gt; два рекурсивных обращения к &#039;&#039;dc_eig&#039;&#039;&amp;lt;math&amp;gt;(T_{i},Q_{i},\Lambda_{i})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных значений матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +O(n^{2})&amp;lt;/math&amp;gt; вычисление собственных векторов матрицы &amp;lt;math&amp;gt;D+\rho uu^{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; +c*n^{3}&amp;lt;/math&amp;gt; вычисление матрицы &amp;lt;math&amp;gt;Q = \begin{bmatrix} Q_{1} &amp;amp; 0 \\ 0 &amp;amp; Q_{2}\end{bmatrix}*Q^{&#039;}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt; Q_{1}, Q_{2}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; рассматриваются как плотные матрицы и используется стандартный алгоритм матричного умножения, то константа &amp;lt;math&amp;gt; c &amp;lt;/math&amp;gt; в последней строке равна 1. Таким образом, именно это умножение составляет наиболее трудоёмкую часть алгоритма в целом. Игнорируя члены порядка &amp;lt;math&amp;gt;n^{2}&amp;lt;/math&amp;gt;, получаем &amp;lt;math&amp;gt;t(n) = 2t(n/2) + cn^{3}&amp;lt;/math&amp;gt;. Решая это разностное уравнение, находим &amp;lt;math&amp;gt; t \approx c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На практике константа &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; обычно гораздо меньше 1, потому что матрица &amp;lt;math&amp;gt;Q^{&#039;}&amp;lt;/math&amp;gt; весьма разрежена вследствие явления, называемого дефляцией.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
В данном разделе представлен информационный граф алгоритма: на рисунке 5 изображена часть алгоритма, в которой происходит деление - &amp;quot;Divide&amp;quot;, а на рисунке 6 описана часть &amp;quot;Conquer&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[[File:Divide.jpg|thumb|center|left|800px|Рис. 5. Информационный граф алгоритма &amp;quot;Разделяй и властвуй&amp;quot; - шаг &amp;lt;b&amp;gt;Divide&amp;lt;/b&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
[[File:ConquerPart.jpg|thumb|center|1200px|Рис. 6. Информационный граф алгоритма &amp;quot;Разделяй и властвуй&amp;quot; - шаг &amp;lt;b&amp;gt;Conquer&amp;lt;/b&amp;gt;. Qk*k - матрица собственных значений размерности &amp;lt;b&amp;gt;k&amp;lt;/b&amp;gt;. Lk*k - матрица собственных векторов размерности &amp;lt;b&amp;gt;k&amp;lt;/b&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Операции: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - операция Divide разделения матрицы &amp;lt;math&amp;gt; T &amp;lt;/math&amp;gt; на матрицы &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Индексы - размерность &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_{k}&amp;lt;/math&amp;gt; - операция Conquer сбора матрицы &amp;lt;math&amp;gt; D &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; L_{k} &amp;lt;/math&amp;gt; - операция поиска собственных значений &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S^{&#039;} &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q^{&#039;} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; - операция поиска собственных векторов матрицы &amp;lt;math&amp;gt; Q &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Рассмотрев информационный граф алгоритма, можно заметить, что в структуре имеется два параллельных блока - вызов рекурсивных функций &#039;&#039;dc_eig&#039;&#039; для вычисления собственных значений и векторов матриц &amp;lt;math&amp;gt;T_{1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;T_{2}&amp;lt;/math&amp;gt; - это единственная часть алгоритма, в которой мы прибегаем к параллелизму. &lt;br /&gt;
&lt;br /&gt;
Рекурсивное разделение матрицы ведёт к иерархии вычислительных подблоков с графом зависимости данных, который выглядит, как двоичное дерево. Данная структура является естественным путем разделения вычислений между процессорами.&lt;br /&gt;
&lt;br /&gt;
В вершине дерева все процессы взаимодействуют. В каждой ветви дерева вычисление делится на два вычислительных подблока, внутри каждого из которых процессы параллельно взаимодействуют. В листьях деревьев каждый процесс занимается вычислением независимо.&lt;br /&gt;
&lt;br /&gt;
В случае реализации без параллелизма, функции &#039;&#039;dc_eig&#039;&#039; отрабатывают последовательно - сначала со входными параметрами &amp;lt;math&amp;gt;T_{1}, Q_{1}, \Lambda_{1}&amp;lt;/math&amp;gt;, затем - &amp;lt;math&amp;gt;T_{2}, Q_{2}, \Lambda_{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, благодаря параллелизму высота алгоритма параллельного составляет &amp;lt;math&amp;gt;log_{2}(n)&amp;lt;/math&amp;gt;, когда как при последовательной реализации высота алгоритма равна сумме геометрической прогрессии &amp;lt;math&amp;gt;1+2+4+8+...+2^{n-1} = 2^{n} - 1&amp;lt;/math&amp;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;
&amp;lt;math&amp;gt;&lt;br /&gt;
L = \begin{bmatrix}&lt;br /&gt;
 a_{1} &amp;amp; b_{1}&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \\&lt;br /&gt;
 b_{1} &amp;amp; \ddots  &amp;amp; \ddots \\&lt;br /&gt;
   &amp;amp; \ddots &amp;amp; a_{m-1} &amp;amp; b_{m-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp; b_{m-1} &amp;amp; a_{m} &amp;amp; b_{m} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp; b_{m} &amp;amp; a_{m+1} &amp;amp; b_{m+1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{m+1} &amp;amp; \ddots \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; \ddots &amp;amp; b_{n-1} \\&lt;br /&gt;
 &amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp; b_{n-1} &amp;amp; a_{n} \\&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объем входных данных:&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\frac{n (n + 1)}{2}&amp;lt;/math&amp;gt; - в силу симметрии, хранить следует только диагональные, поддиагональые и наддиагональные элементы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
На выходе мы получаем вектор собственных значений длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; собственных векторов длины &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; , соответствующих собственным значениям исходной матрицы.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объем выходных данных:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; n + n^{2} = n(n + 1) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
Описанный в данной статье алгоритм является самым быстрым алгоритмом, среди существующих: QR / Бисекция и обратная итерация / Разделяй и властвуй.&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма: &amp;lt;math&amp;gt; t = c\frac{4}{3}n^{3} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Параллельная сложность алгоритма (в силу использования дефляции): &amp;lt;math&amp;gt; O(n^{2.3})&amp;lt;/math&amp;gt;  или в самых редких случаях &amp;lt;math&amp;gt; O(n^{2}) &amp;lt;/math&amp;gt; (см. [3] стр. 8).&lt;br /&gt;
&lt;br /&gt;
Особенности алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. Использование дефляции ([[#Дефляция]])&lt;br /&gt;
&lt;br /&gt;
2. Использование адаптированного метода Ньютона ([[#Макроструктура алгоритма]])&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Программная реализация алгоритма =&lt;br /&gt;
Вторая часть описания алгоритмов в рамках AlgoWiki рассматривает все составные части процесса их реализации. Рассматривается как последовательная реализация алгоритма, так и параллельная. Описывается взаимосвязь свойств программ, реализующих алгоритм, и особенностей архитектуры компьютера, на которой они выполняются. Исследуется работа с памятью, локальность данных и вычислений, описывается масштабируемость и эффективность параллельных программ, производительность компьютеров, достигаемая на данной программе. Обсуждаются особенности реализации для разных классов архитектур компьютеров, приводятся ссылки на реализации в существующих библиотеках.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
TBD&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Большого количества реализаций данного алгоритма найдено не было: единственный пример описан на страницах 7, 19 и 20 в [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf этой статье], которая указана в списке литературы.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Дж. Деммель, «Вычислительная линейная алгебра» //С. 230-235&lt;br /&gt;
&lt;br /&gt;
[2] [http://www.netlib.org/utk/people/JackDongarra/PAPERS/104_1999_a-parallel-divide-and-conquer-algorithm.pdf Francoise Tisseury, Jack Dongarra, A Parallel Divide and Conquer Algorithm for the Symmetric Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[3] [http://www.netlib.org/lapack/lawnspdf/lawn132.pdf Francoise Tisseury, Jack Dongarra, Parallelizing the Divide and Conquer Algorithm for the Symmetric Tridiagonal Eigenvalue Problem on Distributed Memory Architectures]&lt;br /&gt;
&lt;br /&gt;
[4] [https://en.wikipedia.org/wiki/Divide-and-conquer_eigenvalue_algorithm Алгоритм &amp;quot;Разделяй и властвуй&amp;quot; - Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[5] [http://www.cscamm.umd.edu/tadmor/pub/linear-stability/Gill_Tadmor_SISC90.pdf Doron Grill and Eitan Tadmor AN &amp;lt;math&amp;gt;O(N2)&amp;lt;/math&amp;gt; METHOD FOR COMPUTING THE EIGENSYSTEM OF &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt;x&amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; SYMMETRIC TRIDIAGONAL MATRICES BY THE DIVIDE AND CONQUER APPROACH]&lt;br /&gt;
&lt;br /&gt;
[[en:Description of algorithm properties and structure]]&lt;/div&gt;</summary>
		<author><name>IgorS</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=15395</id>
		<title>Участник:Kanast74/Алгоритм кластеризации, основанный на минимальном покрывающем дереве</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:Kanast74/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8,_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%B0_%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC_%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%BC_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5&amp;diff=15395"/>
		<updated>2016-11-01T07:20:57Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
Статью подготовила: [[Участник:kanast74|Дмитриева Анастасия Владимировна]].&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритмов ==&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;X=\{x_1...x_n\}&amp;lt;/math&amp;gt;, а также функция расстояния между ними &amp;lt;math&amp;gt;r(e)&amp;lt;/math&amp;gt;. Отметим, что метрика, в которой вычисляются расстояния между объектами, выбирается исходя из требований задачи; наиболее распространенные - это Евклидово расстояние, квадрат Евклидова расстояния, манхэттенская метрика, расстояние Чебышева и степенное расстояние. Далее необходимо применить один из алгоритмов кластеризации, которые можно разделить на две категории: иерархические, состоящие в последовательном поиске кластеров из уже найденных кластеров, а также неиерархические, цель которых состоит в минимизации некоторой целевой функции. К последней группе относятся такие алгоритмы, как алгоритм k-средних, алгоритм EM, нечеткие алгоритмы, а также алгоритмы теории графов, к категории которых относится алгоритм, описываемый в данной статье.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим алгоритм кластеризации на k кластеров, основанный на построении минимального остовного дерева (MST) и который состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
# По исходным данным строится взвешенный неориентированный граф: каждый объект представляется в виде вершины графа, а вес ребра, связывающего две вершины, есть расстояние между объектами в выбранной заранее метрике&lt;br /&gt;
# Для полученного графа строится минимальное остовное дерево, то есть дерево, содержащее все вершины графа и имеющее минимальный суммарный вес своих ребер&lt;br /&gt;
# Удаляется k-1 ребро минимального остовного дерева с максимальным весом&lt;br /&gt;
# Получаются k компонент связности, которые интерпретируются как кластеры&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;nowiki&amp;gt;, наиболее подробно изучить который можно, пройдя по ссылке [&amp;lt;/nowiki&amp;gt;[https://algowiki-project.org/ru/Алгоритм_Крускала Алгоритм Крускала]], поэтому в данной статье его описание не приводится.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
С математической точки зрения задачу кластеризации можно рассматривать как минимизацию среднего внутрикластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i)=c(x_j)} 1} \to min &amp;lt;/math&amp;gt; с одной стороны и максимизации межкластерного расстояния &amp;lt;math&amp;gt; \frac{\sum_{i&amp;lt;j, c(x_i)\neq c(x_j)} r(x_i,x_j)}{\sum_{i&amp;lt;j, c(x_i) \neq c(x_j)} 1} \to max &amp;lt;/math&amp;gt; с другой стороны, где &amp;lt;math&amp;gt;c(x)&amp;lt;/math&amp;gt; - кластер, к которому принадлежит объект &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Применительно к алгоритмам кластеризации, основанным на теории графов, процедура минимизации/максимизации соответствующих расстояний состоит в выборе определенного порогового значения веса ребра &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;, ребра с весом меньше которого предполагаются связующими для вершин, принадлежащих одному классу, а ребра с весом больше которого предполагаются связующими для вершин, принадлежащих разным классам.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром описываемого алгоритма является построение минимального остовного дерева. В случае, если программа предполагается быть распараллеленной, то операция построения MST будет производиться в отдельности в каждом процессе. Как уже было отмечено, в данном исследовании для построения MST использовался алгоритм Крускала, который будет работать наиболее эффективно, если веса ребер исходного графа отсортированы, поэтому сортировка - это еще одна операция, которая должна быть включена в вычислительное ядро, что предполагает выбранная ревлизация алгоритма. Поэтому в дальнейшем мы не будем разделять сортировку и поиск минимального остовного дерева, предполагая, что они объеденены в единый эффективный алгоритм. &lt;br /&gt;
&lt;br /&gt;
Дадим некоторую справку касательно адгоритма поиска MST, фигурирующего в данном исследовании. Алгоритм Крускала — алгоритм построения минимального остовного дерева взвешенного связного неориентированного графа. Алгоритм впервые описан Джозефом Крускалом в 1956 году.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Крускала находит безопасное ребро для добавления в растущий лес путем поиска ребра &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; с минимальным весом среди всех ребер, соединяющих два дерева в лесу. Обозначим два дерева, соединяемые ребром &amp;lt;math&amp;gt;(u, v) &amp;lt;/math&amp;gt;, как &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;(u, v)&amp;lt;/math&amp;gt; — безопасное для &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; ребро. Алгоритм Крускала является жадным, поскольку на каждом шаге он добавляет к лесу ребро с минимально возможным весом.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
# Построение графа путем задания списков ребер. В общем случае, когда задано некоторое множество объектов, для построения графа может быть использован алгоритм, позволяющий рассчитать расстояние от каждого объекта до каждого за &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; число объектов (вообще говоря, данная операция требует &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt; операций, где &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; - число ребер в графе, но в нашем случае число ребер подразумевается равным &amp;lt;math&amp;gt;E=\frac{D (n(n-1))}{2}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; - плотность графа). Однако в рамках данного исследования для тестирования алгоритма использовалась такая технология построения графов, когда в качестве веса каждого ребра назначается некоторое произвольное число из заданного промежутка (например, от 1 до 1000), что также требует &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
# Построение минимального остовного дерева на основании полученного ранее графа, для чего может быть использован один из известных и наиболее распространенных алгоритмов (алгоритм Крускала, алгоритм Борувки или алгоритм Прима). Рассмотрим, например, алгоритм Крускала. На начальном этапе имеется граф, состоящий из вершин - объектов исходной выборки и не содержащий ребер, следовательно, каждая из вершин изначально представляет собой компоненту связности и даже дерево. Алгоритм состоит в выборе на каждом шаге ребра с минимальным весом и добавлении его в дерево, если оно соединяет два дерева. Алгоритм будет работать максимально эффективно, если изначально ребра графа, полученного на первом шаге будут отсортированы (например, по возрастанию). &lt;br /&gt;
# Для получения &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров необходимо удалить из получившегося минимального остовного дерева &amp;lt;math&amp;gt;k-1&amp;lt;/math&amp;gt; ребро с максимальным весом. В частности, в случае, если на предыдущем шаге мы пользовались алгоритмом Крускала для построения MST, ребра получившегося дерева отсортированы по возрастанию, значит для удаления указанных выше ребер потребуется лишь &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
# Наконец, необходимо правильно интерпретировать результат работы алгоритма и перечислить вершины, принадлежащие полученным кластерам; на выполнение данного шага может потребоваться различное число операций в зависимости от формата выходных данных программы: например, в данной реализации выходные данные, как будет сказано в дальнейшем, представлены в виде списка ребер, формирующий итоговый минимальный остовный лес, следовательно в общем случае на его обработку потребуется &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
==== Используемые обозначения и структуры данных ====&lt;br /&gt;
&lt;br /&gt;
Обозначим в качестве &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; граф, содержащий &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; вершин и &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; ребер. Для построения минимального остовного дерева для данного графа используются три операции [2], опишем здесь каждую из них:&lt;br /&gt;
&lt;br /&gt;
# операция MakeSet(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) генерирует дерево (множество) для вершины графа &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, являющейся корнем искомого дерева&lt;br /&gt;
# операция Find(&amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;) определяет, которому из деревьев принадлежит интересующая вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Условимся, что данная операция возвращает ту вершину, которая является корнем искомого дерева&lt;br /&gt;
# операция Union(&amp;lt;math&amp;gt;u, v&amp;lt;/math&amp;gt;) объединяет два множества (два дерева, содержащих соответствующие вершины) под одним корнем&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма === &lt;br /&gt;
&lt;br /&gt;
Приведем здесь псевдокод основной части алгоритма - а именно построения локального минимального остовного дерева при помощи алгоритма Крускала.&lt;br /&gt;
&lt;br /&gt;
Kruskal ( G=(V,E) ):    &lt;br /&gt;
&lt;br /&gt;
1. mst = ∅;    &lt;br /&gt;
&lt;br /&gt;
2. сортировать E по возрастанию;&lt;br /&gt;
&lt;br /&gt;
3. для каждой вершины v из множества вершин V:&lt;br /&gt;
&lt;br /&gt;
4. MakeSet(v);&lt;br /&gt;
&lt;br /&gt;
5. для каждого ребра e = (u, v) из множества ребер E:&lt;br /&gt;
&lt;br /&gt;
6.    if Find(u) ≠ Find(v):&lt;br /&gt;
&lt;br /&gt;
7.    mst = mst ∪ { (u,v) }; &lt;br /&gt;
&lt;br /&gt;
8.    Union(u, v); &lt;br /&gt;
&lt;br /&gt;
9. return mst;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Сперва оценим последовательную сложность составных частей алгоритма.&lt;br /&gt;
&lt;br /&gt;
0. O(E): Формирование графа&lt;br /&gt;
&lt;br /&gt;
1. O(1): Инициализация пустого множества. &lt;br /&gt;
&lt;br /&gt;
2. O(Elog(E)): сортировка ребер из множества E. &lt;br /&gt;
&lt;br /&gt;
3. O(V): Инициализация одноточечного множества для каждой вершины из V. &lt;br /&gt;
&lt;br /&gt;
4.    ▲ MakeSet за O(1). &lt;br /&gt;
&lt;br /&gt;
5. O(Elog(V)): Операции Union и Find для каждого ребра из E. &lt;br /&gt;
&lt;br /&gt;
6.    ▲ Find за O(1). &lt;br /&gt;
&lt;br /&gt;
7.    ▲ Присоединение элемента за O(1). &lt;br /&gt;
&lt;br /&gt;
8.    ▲ Union за O(log(V)).&lt;br /&gt;
&lt;br /&gt;
9. O(1): Удаление k-1 ребра для получения k компонент связности&lt;br /&gt;
&lt;br /&gt;
10. O(V): Перечисление вершин, входящих в соответствующие кластеры&lt;br /&gt;
&lt;br /&gt;
11. O(1): Return.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Таким образом, с учетом того, что, получаем:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T( G = (V, E) ) = O(1) + O(E\log(E)) + O(V) + O(E\log(V)) + O(E) = O(E\log(E)) + O(E\log(V))  = O(E\log(V)) + O(E\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Итак, оценка сложности алгоритма есть &amp;lt;math&amp;gt;T( G = (V, E) ) = O(E\log(V)) + O(\log(V))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
[[file:Clustering kruskal.png|thumb|center|1400px|Рисунок 1. Информационный граф алгоритма]]&lt;br /&gt;
&lt;br /&gt;
По структуре информационного графа можно сделать вывод что высота канонической ярусно-параллельной формы [1] есть &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; число процессов.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Описанный алгоритм позволяет произвести распараллеливание, источник которого будет описан в данном пункте. Прежде всего, необходимо отметить, что данный алгоритм допускает преобразование в гибридную параллельную версию, а именно версию с использованием технологий MPI и OpenMP. &lt;br /&gt;
&lt;br /&gt;
Тем не менее, прежде всего следует установить справедливость свойства используемого алгоритма поиска MST, позволяющего произвести его распараллеливание [3]: &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ассоциативность по рёбрам&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;MSF(E)&amp;lt;/math&amp;gt; – минимальный остовный лес графа с рёбрами &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;. Тогда&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        MSF(E_1 \cup E_2 \cup \dots \cup E_k) = MSF(MSF(E_1) \cup MSF(E_2) \cup \dots \cup MSF(E_k)).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В том случае, когда все объекты заранее известны, и между ними можно рассчитать все попарные расстояния, первый шаг - построение графа - позволяет произвести распараллеливание при помощи обеих вышеуказанных технологий, а именно: разделить объекты между процессами при помощи MPI, а внутри каждой из полученных групп будем рассчитывать попарные расстояния параллельно на нескольких ядрах, использовав технологию OpenMP. Однако в данном исследовании кластеризуемые объекты предполагались заранее неизвестными, так как попарные расстояния между объектами задаются случайным образом, а это означает, что произвести распараллеливание этого шага, которое бы дало хотя бы небольшое ускорение (уменьшение времени работы), не получится.&lt;br /&gt;
&lt;br /&gt;
Если все же объекты заранее были известны, можно сохранить полученную на предыдущем этапе группировку их на отдельные процессы и внутри каждого произвести сортировку расстояний между объектами по возрастанию при помощи алгоритма QSort с использованием технологии OpenMP (в противном случае параллельная структура алгоритма поддерживается начиная с этого шага).&lt;br /&gt;
&lt;br /&gt;
Далее внутри каждой группы объектов (каждый процесс в отдельности) строим минимальное остовное дерево, в конце шага деревья объединяются в одно - общее MST для всех объектов, поддерживая при этом отсортированность расстояний по возрастанию.&lt;br /&gt;
&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  &amp;lt;math&amp;gt; \log{p} &amp;lt;/math&amp;gt; шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
==== Способы разделения графа ====&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;G = (V, E)&amp;lt;/math&amp;gt;, являющийся неориентированным, взвешенным, связным и не имеющим кратных ребер. Пусть полученный граф содержит &amp;lt;math&amp;gt; |V|&amp;lt;/math&amp;gt; вершин и  &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; ребер. Предполагая, что каждый объект можно сравнить с каждым, можно считать, что получаемый граф является полным, а значит число ребер может быть оценено следующим образом: &amp;lt;math&amp;gt;|E|=\frac{|V|(|V|-1)}{2} \le \frac{|V|^2}{2}-\frac{|V|}{2} \Rightarrow O(E)=O(V^2)&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Обозначим в виде &amp;lt;math&amp;gt;w(u,v)&amp;lt;/math&amp;gt; вес ребра, соединяющего вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Тогда граф может быть задан в виде списка ребер, а именно списка троек: &amp;lt;math&amp;gt;(u,v,w(u,v))&amp;lt;/math&amp;gt;, что является более удобным представлением графа для реализации многих алгоритмов по сравнению с матрицей смежности. Помимо этого, данный способ помогает сэкономить память. Недостатков указанного подхода является только то, что установить, с какими вершинами связана интересующая вершина, а также вес соответствующих ребер является весьма затруднительно. Однако для решения поставленной задачи нам это и не потребуется.&lt;br /&gt;
&lt;br /&gt;
Формат выходных данных аналогичный&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
==== Вычислительная мощность ====&lt;br /&gt;
Оценим вычислительную мощность алгоритма в том случае, когда для его реализации используется &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов. Предположим, что алгоритм реализован при помощи первой стратегии (см. выше) и применяется к графу &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt;, тогда:&lt;br /&gt;
&lt;br /&gt;
* Ввод данных: &amp;lt;math&amp;gt;\frac{|E|}{p}&amp;lt;/math&amp;gt; - в среднем столько ребер содержит получаемый каждым процессом связный подграф&lt;br /&gt;
* Вывод данных: &amp;lt;math&amp;gt;\frac{|V| - 1}{p}&amp;lt;/math&amp;gt; - каждый из процессов должен вывести посчитанное локальное минимальное дерево, которое содержит указанное количество ребер, так как остальные ребра, изначально входившие в состав подграфа, не будут входит в локальное MST&lt;br /&gt;
* Вычисление: для вычисления локального MST каждому из процессов требуется &amp;lt;math&amp;gt;\frac{|E|}{p} \log{\frac{|V|}{p}}&amp;lt;/math&amp;gt; операций&lt;br /&gt;
&lt;br /&gt;
Таким образом, искомое соотношение может быть записано в следующем виде: &amp;lt;math&amp;gt;\frac{\frac{|E|}{p} \log{\frac{|V|}{p}}}{\frac{|E|}{p} + \frac{|V| - 1}{p}} = \frac{|E| \log{\frac{|V|}{p}}}{|E|+|V|-1}&amp;lt;/math&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;
В данном рзеделе будет производиться оценка прироста производительности параллельного алгоритма по сравнению с последовательным. Наиболее простой способ оценить прирост производительности – использовать закон Амдала, который гласит, что прирост может быть оценен следующим образом: &amp;lt;math&amp;gt; S_p \le \frac{1}{f+\frac{1-f}{p}}&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; – часть алгоритма, которая не может быть распараллелена, &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; – число процессов.&lt;br /&gt;
&lt;br /&gt;
В наиболее тривиальном случае (первая стратегия распараллеливания) распараллеливанию не подлежит считывание входных данных и их разделение между процессами, а также объединение локальных MST в одно. Поэтому сложность последовательной части программы может быть оценена следующим образом: &amp;lt;math&amp;gt;2O(V^2)+O(V^2 \log{V})&amp;lt;/math&amp;gt;. Распараллеливанию подлежит инициализация подграфов при помощи операции MakeSet за &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt;, сортировка ребер по возрастанию внутри каждой группы за &amp;lt;math&amp;gt;O(V^2 \log{V})&amp;lt;/math&amp;gt;, а также построение локальных минимальных остовных деревьев за аналогичное время. Поэтому сложность параллельной части алгоритма может быть оценена так:  &amp;lt;math&amp;gt;O(V)+2 O(V^2 \log{V})&amp;lt;/math&amp;gt;. Таким образом, вычислив отношение сложностей частей программы, получаем, что  &amp;lt;math&amp;gt;f=0.44&amp;lt;/math&amp;gt;. Устремим число процессов  &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; к бесконечности и получим, что максимальное ускорение, которое можно получить путем распараллеливания алгоритма есть &amp;lt;math&amp;gt;2.27&amp;lt;/math&amp;gt;. К сожалению, данная оценка не является точной. Для того, чтобы оценить прирост производительности более качественно, произведем анализ временных диаграмм программы.&lt;br /&gt;
&lt;br /&gt;
===== Анализ временных диаграмм =====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;t_d&amp;lt;/math&amp;gt; – время, которое требуется для того, чтобы разделить граф на подграфы, &amp;lt;math&amp;gt;t_p&amp;lt;/math&amp;gt; – суммарное время вычислений, &amp;lt;math&amp;gt;t_b&amp;lt;/math&amp;gt; – время обратной пересылки локально полученных результатов, &amp;lt;math&amp;gt;t_l&amp;lt;/math&amp;gt; – задержка одной пересылки. Причем можно убедиться в том, что &amp;lt;math&amp;gt;t_p &amp;gt; t_d &amp;gt; t_b &amp;gt; t_l&amp;lt;/math&amp;gt;, также известно, что время выполнения последовательного кода &amp;lt;math&amp;gt;t_{serial} = t_p&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:FirstStrategy.PNG|thumb|center|400px|Рисунок 2. Первая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Произведем анализ первой (наивной) стратегии распараллеливания программы. На временной диаграмме жирным обозначен критический путь, время выполнения которого может быть вычислено по формуле (см: [2]):&lt;br /&gt;
&amp;lt;math&amp;gt;t_{paralel} = 4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда максимальное ускорение для одного процесса есть: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{t_p}{4 t_l + t_d + \frac{t_p}{3} + \frac{t_b}{3}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А в случае n процессов: &amp;lt;math&amp;gt;speedup = \frac{n}{1+\frac{n (n+1) t_l + n t_d + t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:SecondStrategy.PNG|thumb|center|400px|Рисунок 3. Вторая стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Теперь проанализируем вторую стратегию, основная задача которой – уменьшить объем коммуникаций между процессами за счет того, что файл с исходными данными теперь доступен всем процессам. Помимо этого введем в рассмотрение новую величину &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; – время, требующееся на инициализацию множеств вершин. Тогда для одного процесса &amp;lt;math&amp;gt;t_{parallel} = t_i + \frac{t_p}{4} + t_l + 3 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а максимальное ускорение в случае n процессов есть: &amp;lt;math&amp;gt;speedup= \frac{t_{serial}}{t_{parallel}} = \frac{4}{1+\frac{4 t_i + 4 t_l + 3 t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:ThirdStrategy.PNG|thumb|center|400px|Рисунок 4. Третья стратегия распараллеливания вычислительного ядра алгоритма]]&lt;br /&gt;
Наконец, проанализируем третью стратегию, основная цель которой – оптимизировать процесс слияния локальных минимальных остовных деревьев, а также привлечь все активные процессы (включая главный) к процессу построения локального MST. В этом случае время выполнения параллельной части программы с использованием лишь одного процесса: &amp;lt;math&amp;gt; t_{parallel}=t_i + \frac{t_p}{4} + t_l + 2 \frac{t_b}{4}&amp;lt;/math&amp;gt;, а суммарное ускорение в общем случае: &amp;lt;math&amp;gt;speedup=\frac{t_{serial}}{t_{parallel}} = \frac{n}{1+\frac{n t_i + n t_l + \log_2{n} t_b}{t_p}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На рисунке 5 приведены графики, отражающие зависимость получаемых возможных ускорений в зависимости от числа задействованных процессов, посчитанные с использованием четырех различных подходов.&lt;br /&gt;
[[Файл:Comparison.PNG|thumb|center|400px|Рисунок 5. Сравнение стратегий распараллеливания]]&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&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;p=2^n,n&amp;gt;1&amp;lt;/math&amp;gt;. В этом случае потребуется &amp;lt;math&amp;gt;\log{p}&amp;lt;/math&amp;gt; шагов, чтобы выполнить слияние. На каждом шаге половина активных процессов пересылает свое локальное дерево процессу с меньшим номером на единицу, а другая половина занимается приемом и слиянием. Таким образом, через  шагов главный процесс, который имеет номер 0, будет иметь в своем распоряжении глобальное минимальное остовное дерево.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
[[file:Total.png|thumb|center|400px|Рисунок 6. График зависимости времени вычисления от размера задачи и числа процессов (с учетом времени ввода-вывода)]]&lt;br /&gt;
[[file:Wo_io.png|thumb|center|400px|Рисунок 7. График зависимости времени вычисления от размера задачи и числа процессов (без учета времени ввода-вывода)]]&lt;br /&gt;
&lt;br /&gt;
На графиках видно, как с увеличением объема вычислительных ресурсов происходит прирост в производительности. Данный эффект происходит до некоторых пор, после чего накладные расходы на коммуникацию между процессами нивелируют его. При дальнейшем увеличении числа процессов время решения задачи заметно увеличивается, однако, ввиду длинных очередей на суперкомпьютере Ломоносов данные с числом процессов &amp;lt;math&amp;gt; \ge 256&amp;lt;/math&amp;gt; получить не удалось&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма  [3]===&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/kruskal_min_spanning_tree.html kruskal_min_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#merge-local-minimum-spanning-trees merge_local_minimum_spanning_trees]&amp;lt;/code&amp;gt; реализует алгоритм Крускала;&lt;br /&gt;
** функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают [[алгоритм Борувки]] и алгоритм Крускала.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.mst.minimum_spanning_tree.html minimum_spanning_tree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/KruskalMinimumSpanningTree.html KruskalMinimumSpanningTree]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
[1] Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. - СПб.: БХВ-Петербург, 2002. - 608 с.&lt;br /&gt;
&lt;br /&gt;
[2] http://www.gpiskas.com/pdf/kruskal_mpi.pdf (дата обращения: 02.10.2016)&lt;br /&gt;
&lt;br /&gt;
[3] https://algowiki-project.org/ru/Алгоритм_Крускала&lt;/div&gt;</summary>
		<author><name>IgorS</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:%D0%9B%D1%83%D0%BA%D1%8C%D1%8F%D0%BD%D0%BE%D0%B2%D0%B0_%D0%90%D0%BD%D0%BD%D0%B0/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D1%81_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC_%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B92&amp;diff=15392</id>
		<title>Участник:Лукьянова Анна/Алгоритм кластеризации с использованием представлений2</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:%D0%9B%D1%83%D0%BA%D1%8C%D1%8F%D0%BD%D0%BE%D0%B2%D0%B0_%D0%90%D0%BD%D0%BD%D0%B0/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D1%81_%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC_%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B92&amp;diff=15392"/>
		<updated>2016-11-01T07:14:32Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[U:Лукьянова Анна|А.Я. Лукьянова]]&lt;br /&gt;
&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмa =&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
===Понятие кластеризации===&lt;br /&gt;
&lt;br /&gt;
Кластеризация (или кластерный анализ) — это задача разбиения множества объектов на группы, называемые кластерами. Внутри каждой группы должны оказаться «похожие» объекты, а объекты разных группы должны быть как можно более отличны. Главное отличие кластеризации от классификации состоит в том, что перечень групп четко не задан и определяется в процессе работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Применение кластерного анализа в общем виде сводится к следующим этапам:&lt;br /&gt;
&lt;br /&gt;
#Отбор выборки объектов для кластеризации.&lt;br /&gt;
#Определение множества переменных, по которым будут оцениваться объекты в выборке. При необходимости – нормализация значений переменных.&lt;br /&gt;
#Вычисление значений меры сходства между объектами.&lt;br /&gt;
#Применение метода кластерного анализа для создания групп сходных объектов (кластеров).&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;
&#039;&#039;Иерархические методы:&#039;&#039;&lt;br /&gt;
*Агломеративные методы AGNES (Agglomerative Nesting):&lt;br /&gt;
*Дивизимные методы DIANA (Divisive Analysis):&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Неиерархические методы.&#039;&#039;&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;
&#039;&#039;&#039;Методы по количеству применений алгоритмов кластеризации:&#039;&#039;&#039;&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;
&#039;&#039;&#039;Методы по времени выполнения кластеризации:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*Потоковые (on-line);&lt;br /&gt;
&lt;br /&gt;
*Не потоковые (off-line). &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
===CURE===&lt;br /&gt;
&lt;br /&gt;
Алгоритм &#039;&#039;&#039;CURE (Clustering Using REpresentatives)&#039;&#039;&#039; выполняет иерархическую кластеризацию с использованием набора определяющих точек для определения объекта в кластер. Относится к алгомеративным методам.&lt;br /&gt;
&lt;br /&gt;
Эта группа методов характеризуется последовательным объединением исходных элементов и соответствующим уменьшением числа кластеров.&lt;br /&gt;
&lt;br /&gt;
Назначение: кластеризация очень больших наборов числовых данных.&lt;br /&gt;
&lt;br /&gt;
Ограничения: эффективен для данных низкой размерности, работает только на числовых данных. &lt;br /&gt;
&lt;br /&gt;
Достоинства: &lt;br /&gt;
*выполняет кластеризацию на высоком уровне даже при наличии выбросов;&lt;br /&gt;
*выделяет кластеры сложной формы и различных размеров, &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:Page1.png|thumb|center|1400px|Рисунок 1: Обзор CURE ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 1:&#039;&#039;&#039; Построение дерева кластеров, состоящего из каждой строки входного набора данных.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 2:&#039;&#039;&#039; Формирование «кучи» в оперативной памяти, расчет расстояния до ближайшего кластера (строки данных) для каждого кластера. При формировании кучи кластеры сортируются по возрастанию дистанции от кластера до ближайшего кластера. Расстояние между кластерами определяется по двум ближайшим элементам из соседних кластеров. Для определения расстояния между кластерами используются «манхеттенская», «евклидова» метрики или похожие на них функции.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Метрика Евклида:&#039;&#039; &amp;lt;math&amp;gt;\rho(p,k) = \sqrt{\sum_{i=1}^n (p_{i} - k_{i})^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;«Манхеттенская» метрика:&#039;&#039; &amp;lt;math&amp;gt;\rho(p,k) =\sum_{i=1}^n |p_{i} - k_{i}|&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 3:&#039;&#039;&#039; Слияние ближних кластеров в один кластер. Новый кластер получает все точки входящих в него входных данных. Расчет расстояния до остальных кластеров для новообразованного кластера. Для расчета расстояния кластеры делятся на две группы: первая группа – кластеры, у которых ближайшими кластерами считаются кластеры, входящие в новообразованный кластер, остальные кластеры – вторая группа. И при этом для кластеров из первой группы, если расстояние до новообразованного кластера меньше чем до предыдущего ближайшего кластера, то ближайший кластер меняется на новообразованный кластер. В противном случае ищется новый ближайший кластер, но при этом не берутся кластеры, расстояния до которых больше, чем до новообразованного кластера. Для кластеров второй группы выполняется следующее: если расстояние до новообразованного  кластера ближе, чем предыдущий ближайший кластер, то ближайший кластер меняется. В противном случае ничего не происходит.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Шаг 4:&#039;&#039;&#039; Переход на шаг 3, если не получено требуемое количество кластеров.&lt;br /&gt;
&lt;br /&gt;
Выбранные точки сдвигаются на следующем шаге на &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; к центроиду кластера. Алгоритм становится основанным на методе поиска центроида при &amp;lt;math&amp;gt;\alpha = 1&amp;lt;/math&amp;gt;, и основанным на всех точках кластера при &amp;lt;math&amp;gt;\alpha = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Factor_a.png|400px|thumb|center| Рисунок 2: Чувствительность алгоритма к параметру &amp;lt;math&amp;gt;\alpha &amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Прежде всего, алгоритм использует случайное разбиение выборки из &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; элементов на &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; частей, которые могут поместиться в память отдельной машины кластера. Затем во время первого прохода алгоритм уменьшает число кластеров в каждой подвыборке до &amp;lt;math&amp;gt; \frac{n}{pq} &amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt; q \geq 1 &amp;lt;/math&amp;gt; – некоторая константа. Для последующего шага на вход кластеризации подаются только представительные точки &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;  кластеров, полученных на предыдущем этапе. В ходе второго прохода алгоритма кластеризации остаются представительные точки для заданного количества кластеров. Алгоритм использует представительные точки, как метки, задающие форму и размер кластера.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма представляет собой итерационное построение &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров, вычисление их центроид и назначение точек-представителей.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Основную часть метода составляют разбиение случайной выборки строк на &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; кластеров и нахождение точек-представителей для каждого кластера.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Псевдокод&lt;br /&gt;
&lt;br /&gt;
Input : A set of points S&lt;br /&gt;
&lt;br /&gt;
Output : &#039;&#039;k&#039;&#039; clusters&lt;br /&gt;
&lt;br /&gt;
*   For every cluster u (each input point), in u.mean and u.rep store the mean of the points in the cluster and a set of &#039;&#039;c&#039;&#039; representative points of the cluster (initially &#039;&#039;c&#039;&#039; = 1 since each cluster has one data point). Also u.closest stores the cluster closest to u.&lt;br /&gt;
*   All the input points are inserted into a k-d tree T&lt;br /&gt;
*   Treat each input point as separate cluster, compute u.closest for each u and then insert each cluster into the heap Q. (clusters are arranged in increasing order of distances between u and u.closest).&lt;br /&gt;
*   While size(Q) &amp;amp;gt; &#039;&#039;k&#039;&#039;&lt;br /&gt;
*       Remove the top element of Q(say u) and merge it with its closest cluster u.closest(say v) and compute the new representative points for the merged cluster w.&lt;br /&gt;
*       Remove u and v from T and Q.&lt;br /&gt;
*       For all the clusters x in Q, update x.closest and relocate x&lt;br /&gt;
*           insert w into Q&lt;br /&gt;
*       repeat &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; O(n^2 log(n)) &amp;lt;/math&amp;gt; и  &amp;lt;math&amp;gt; O(n^2) &amp;lt;/math&amp;gt; для признаковых пространств малой размерности &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма все объекты являются отдельными кластерами. На первом шаге наиболее похожие объекты объединяются в кластер. На последующих шагах объединение продолжается до тех пор, пока все объекты не будут составлять один кластер.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Metod.png|400px|thumb|center| Рисунок 3: Дендрограмма агломеративных методов]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Т.к все действия алгорима (вычисления центроид для кластеров, расстояния между кластерами, представители кластеров и изменение положения представителей) на каждом шаге можно делать независимо (как от представителей данного кластера, так и представителей других кластеров), можно сделать вывод, что возможно распараллеливание&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные:&#039;&#039;&#039; набор данных &amp;lt;math&amp;gt; S &amp;lt;/math&amp;gt; , количество кластеров &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;, число точек представителей &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, параметр &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt;, параметр &amp;lt;math&amp;gt;\gamma&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; число точек для кластеризации, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; размерность.&lt;br /&gt;
&lt;br /&gt;
Параметр &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; для сжатия точек представления к центру кластера. В оригинальной статье авторы показывают что &amp;lt;math&amp;gt;\alpha&amp;lt;/math&amp;gt; от &amp;lt;math&amp;gt;0.2&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;0.7&amp;lt;/math&amp;gt; помогает хорошо выявлять несферические кластеры и подавлять редкие выбросы.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Factor_a.png|400px|thumb|center| Рисунок 2: Чувствительность алгоритма к параметру &amp;lt;math&amp;gt;\alpha &amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Параметр &amp;lt;math&amp;gt;\gamma&amp;lt;/math&amp;gt; задаёт какая часть от &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; точек будет использована при случайной выборке точек на шаге 2.&lt;br /&gt;
&lt;br /&gt;
Число точек представителей выбирается в зависимости от данных. Если возможно появление кластеров сложной формы, то точек должно быть больше, чтобы выявить их форму. В оригинальной статье авторы показывают что уже при &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; равном &amp;lt;math&amp;gt;10&amp;lt;/math&amp;gt; достигается неплохая эффективность.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Param_c.png|400px|thumb|center| Рисунок 4: Результат работы алгоритка в зависимости от количесва представлений &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Из рисунка видно, что при меньших значениях &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; качество работы алгоритма ухудшается&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные:&#039;&#039;&#039; требуемое количество &amp;lt;math&amp;gt; k &amp;lt;/math&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;br /&gt;
&lt;br /&gt;
==Динамические характеристики и эффективность реализации алгоритма==&lt;br /&gt;
&lt;br /&gt;
==Выводы для классов архитектур==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Последовательная реализация CURE-алгоритма для кластеризации для Python/C++ представлена в пакете: [https://github.com/annoviko/pyclustering pyclustering].&lt;br /&gt;
&lt;br /&gt;
Также существует параллельная реализация алгоритма, описанная в сборнике [4]. Алгоритм предложенный в статье использует массив данных состоящий из размеров кластера, его центроида и репрезентативных точек, а при объединении кластеров информацию о новом кластере просто записывают в строчку предыдущего. Также для каждого кластера требуется считать индекс и расстояние до ближайшего кластера. Именно их поиск авторы статьи и предлагают распараллелить.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
&lt;br /&gt;
[1] Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. - СПб.: БХВ-Петербург, 2002. - 608 с.&lt;br /&gt;
&lt;br /&gt;
[2] Theodoridis, Sergios; Koutroumbas, Konstantinos (2006). Pattern recognition. Academic Press. pp. 572–574. ISBN 978-0-12-369531-4.&lt;br /&gt;
&lt;br /&gt;
[3] Guha, Sudipto; Rastogi, Rajeev; Shim, Kyuseok (2001). &amp;quot;CURE: An Efficient Clustering Algorithm for Large Databases&amp;quot; (PDF). Information Systems. 26 (1): 35–58. doi:10.1016/S0306-4379(01)00008-4&lt;br /&gt;
&lt;br /&gt;
[4] Panagiotis E. Hadjidoukas, Laurent Amsaleg. OpenMP Shared Memory Parallel Programming.&lt;br /&gt;
Volume 4315 of the series Lecture Notes in Computer Science pp 289-299&lt;br /&gt;
Parallelization of a Hierarchical Data Clustering Algorithm Using OpenMP&lt;/div&gt;</summary>
		<author><name>IgorS</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=15391</id>
		<title>Участник:Vagabond/Алгоритм быстрого дискретного пребразования Фурье</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:Vagabond/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%B5%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%A4%D1%83%D1%80%D1%8C%D0%B5&amp;diff=15391"/>
		<updated>2016-11-01T07:10:22Z</updated>

		<summary type="html">&lt;p&gt;IgorS: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment|IgorS}}&lt;br /&gt;
&#039;&#039;&#039;Автор&#039;&#039;&#039;: Данилишин А.Р.&lt;br /&gt;
= ЧАСТЬ. Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Главная идея БПФ состоит в сокращении многочисленных повторов сложения и умножения, которые выполняются в ДПФ (повторяющиеся множители: &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; Например при условии, что  &amp;lt;math&amp;gt;(pk)mod(n)=const&amp;lt;/math&amp;gt;, при всех &amp;lt;math&amp;gt;k=\overline{0,n-1}&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;p=\overline{0,n-1}&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Напомним формулу ДПФ: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2kpi\pi}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
За счет использования данного факта, происходит значительное сокращение количества вычислительных операций. В данной статье используется алгоритм Cooley – Tukey, который предполагает собой разбиение исходного множества входных сигналов на два равных подмножества. При этом множество входных сигналов должно быть степенью двойки. Далее операция деления множества продолжается для образовавшихся подмножеств, и так далее. В результате над каждым из полученных подмножеств выполняется БПФ. Данные вычисления можно выполнять независимо друг от друга. Этот факт используется для реализации параллельной версии алгоритма. В общем алгоритм Cooley – Tukey строится из двух основных шагов:&lt;br /&gt;
&lt;br /&gt;
* Преобразование входного массива данных (бит-реверсирование).&lt;br /&gt;
* Вычисление БПФ.&lt;br /&gt;
Приведем более детальное описание БПФ (математическое описание):&lt;br /&gt;
*Пусть &amp;lt;math&amp;gt;e^{\frac{-2kpi\pi}{n}}&amp;lt;/math&amp;gt; = &amp;lt;math&amp;gt;\omega _{n}^{kp}&amp;lt;/math&amp;gt;.&lt;br /&gt;
*Разобьем исходный сигнал &amp;lt;math&amp;gt;x(n)&amp;lt;/math&amp;gt; на два подмножества, с четными &amp;lt;math&amp;gt;x_{1}(2n)&amp;lt;/math&amp;gt; и нечетными &amp;lt;math&amp;gt;x_{2}(2n+1)&amp;lt;/math&amp;gt; номерами.&lt;br /&gt;
Тогда получаем: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}\omega _{n}^{kp}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{(2k+1)p}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Выпишем два свойства омеги:&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{\frac{n}{2}}=e^{2(\frac{-2i\pi}{n})}=\omega_{n}^{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
*&amp;lt;math&amp;gt;\omega_{n}^{\frac{n}{2}}=e^{\frac{-2ni\pi}{2n}}=e^{-i\pi}=-1&amp;lt;/math&amp;gt;&lt;br /&gt;
Воспользуемся первым: &amp;lt;math&amp;gt;y_{p}= \sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{n}^{2kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{n}^{2kp}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}+\omega_{n}^{p}\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;. Или, если обозначить &amp;lt;math&amp;gt;y_{1}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;y_{2}(p)=\sum_{k=0}^{\frac{n}{2}-1}x_{2k+1}\omega _{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;, то получим формулу для вычисления координат изображения БПФ при &amp;lt;math&amp;gt;p=\overline{0...n/2}&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;y_(p)=y_{1}(p)+\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заметим, что &amp;lt;math&amp;gt;y(p); y_{1}(p); y_{2}(p)&amp;lt;/math&amp;gt; обладают свойством периодичности, с периодом &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt;. Покажем это для &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; : &amp;lt;math&amp;gt;y_{1}(p+\frac{n}{2})=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{k(p+\frac{n}{2})}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}\omega_{\frac{n}{2}}^{\frac{kn}{2}}=\sum_{k=0}^{\frac{n}{2}-1}x_{2k}\omega_{\frac{n}{2}}^{kp}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так как: &amp;lt;math&amp;gt;\omega_{\frac{n}{2}}^{\frac{kn}{2}}=e^{\frac{-4kni\pi}{2n}}=e^{-2ki\pi}=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Далее необходимо определить &amp;lt;math&amp;gt;y(p)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;p\geq \frac{n}{2}&amp;lt;/math&amp;gt;. Теперь воспользуемся свойством периодичности обнаруженными нами ранее: &amp;lt;math&amp;gt;y(p+\frac{n}{2})=y_{1}(p+\frac{n}{2})+\omega_{n}^{(p+\frac{n}{2})}y_{2}(p+\frac{n}{2})&amp;lt;/math&amp;gt;. Воспользуемся вторым свойством омеги: &amp;lt;math&amp;gt;y(p)=y_{1}(p)-\omega_{n}^{p}y_{2}(p)&amp;lt;/math&amp;gt;.&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;
Бит-реверсирование – это преобразование двоичного числа путем изменения порядка следования бит в нем на противоположный. Бит-реверсирование применимо только к индексам элементов массива и предназначено для изменения порядка следования этих элементов, при этом значения самих элементов не изменяются. Так, к примеру, четырехмерный вектор &amp;lt;math&amp;gt;(x_{0},x_{1},x_{2},x_{3})&amp;lt;/math&amp;gt; изменится на &amp;lt;math&amp;gt;(x_{0},x_{2},x_{1},x_{3})&amp;lt;/math&amp;gt;. Данная процедура необходима для разделения исходного сигнала на элементы подмножеств с четными и нечетными номерами, которые необходимы для вычисления &amp;lt;math&amp;gt;y_{1}(p)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y_{2}(p)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Вычисление БПФ&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
После выполнения этапа бит-реверсирования выполняются вычисления проиллюстрированные &#039;&#039;на рисунке 2&#039;&#039; в разделе [[#Информационный граф|Информационный граф]]. Рисунок 2 приведен для случая четырехмерного входного сигнала. Он предполагает совершение двух этапов. В общем же количество этапов равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Каждый этап содержит в себе вычисление одной и той же операции &amp;quot;бабочки&amp;quot;. Где на вход подаются два числа &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;, на выходе же получаются &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;. Где &amp;lt;math&amp;gt;U&amp;lt;/math&amp;gt;- это &#039;&#039;коэффициент поворота&#039;&#039;. Общая формула для коэффициента поворота: &amp;lt;math&amp;gt;e^{\frac{-ji\pi}{k}}&amp;lt;/math&amp;gt;. Где &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;- размер шага &amp;quot;бабочки&amp;quot;, &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt;- номер итерации вычислений над группой элементов, размером &amp;lt;math&amp;gt;2k&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;(0\leq j &amp;lt; k )&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из двух вышеописанных этапов БПФ, бит-реверсирование является самым быстровыполнимым. Поэтому вычислительное ядро алгоритма будет лежать именно во втором этапе. А именно, им будет являться вычисление &amp;quot;бабочек&amp;quot;. Количество этапов выполнения БПФ, как описывалось выше, равно &amp;lt;math&amp;gt;log_{2}n&amp;lt;/math&amp;gt;. Учитывая количество этапов, размерность входного сигнала и идею деления на подмножества, всего &amp;quot;бабочек&amp;quot; получается &amp;lt;math&amp;gt;\frac{nlog_{2}n}{2}&amp;lt;/math&amp;gt;. При этом операция умножения выполняется &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; раз. Это связано с тем, что результаты вычисления &amp;quot;бабочки&amp;quot; &amp;lt;math&amp;gt;a+bU&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;a-bU&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Из всего вышесказанного заключаем, что макроструктуру алгоритма легче всего описать при помощи рекурсии: &lt;br /&gt;
*&amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; преобразований Фурье порядка 2&lt;br /&gt;
*умножение &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; пар комплексных чисел&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&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;
*выполнение операции &amp;quot;бабочка&amp;quot;&lt;br /&gt;
*выполнение БПФ&lt;br /&gt;
*функция вывода результата (для тестирования)&lt;br /&gt;
----&lt;br /&gt;
&#039;&#039;&#039;главный программный код БПФ:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; *inputSignal = NULL;   &lt;br /&gt;
	complex&amp;lt;double&amp;gt; *outputSignal = NULL; &lt;br /&gt;
	int size = 0;&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;FFT. Serial version.&amp;quot; &amp;lt;&amp;lt; endl;       &lt;br /&gt;
	ProcessInitialization(inputSignal, outputSignal, size);&lt;br /&gt;
	bit(inputSignal, outputSignal, size);&lt;br /&gt;
	SerialFFTCalculation(outputSignal, size);&lt;br /&gt;
	PrintSignal(outputSignal, size); //в случае тестирования&lt;br /&gt;
	ProcessTermination(inputSignal, outputSignal);&lt;br /&gt;
	cin.get();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
=== функция бит-реверсирования ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void bit(complex&amp;lt;double&amp;gt; inputSignal[], complex&amp;lt;double&amp;gt; *outputSignal, int size)&lt;br /&gt;
{ // процесс бит-реверсирования&lt;br /&gt;
	int j = 0, i = 0;&lt;br /&gt;
	while (i &amp;lt; size) &lt;br /&gt;
	{&lt;br /&gt;
		if (j &amp;gt; i) &lt;br /&gt;
		{ &lt;br /&gt;
			outputSignal[i] = inputSignal[j];       &lt;br /&gt;
			outputSignal[j] = inputSignal[i]; &lt;br /&gt;
		}&lt;br /&gt;
		else       &lt;br /&gt;
			if (j == i)          &lt;br /&gt;
				outputSignal[i] = inputSignal[i];&lt;br /&gt;
		&lt;br /&gt;
		int m = size &amp;gt;&amp;gt; 1;     &lt;br /&gt;
		while ((m &amp;gt;= 1) &amp;amp;&amp;amp; (j &amp;gt;= m)) &lt;br /&gt;
		{ &lt;br /&gt;
			j -= m;       &lt;br /&gt;
			m = m &amp;gt;&amp;gt; 1; &lt;br /&gt;
		}     &lt;br /&gt;
		j += m;     &lt;br /&gt;
		i++;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация размерности входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessInitialization(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal, int &amp;amp;size) &lt;br /&gt;
{&lt;br /&gt;
	// инициализация размера входного сигнала  &lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		cout &amp;lt;&amp;lt; &amp;quot;Enter the input signal length: &amp;quot;;&lt;br /&gt;
		cin &amp;gt;&amp;gt; size;&lt;br /&gt;
		if (size &amp;lt; 4)&lt;br /&gt;
			cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be &amp;gt;= 4&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
		else&lt;br /&gt;
		{&lt;br /&gt;
			int tmpSize = size;&lt;br /&gt;
			while (tmpSize != 1)&lt;br /&gt;
			{&lt;br /&gt;
				if (tmpSize % 2 != 0)&lt;br /&gt;
				{&lt;br /&gt;
					cout &amp;lt;&amp;lt; &amp;quot;Input signal length should be powers of two&amp;quot; &amp;lt;&amp;lt; endl;&lt;br /&gt;
					size = -1;&lt;br /&gt;
					break;&lt;br /&gt;
				}&lt;br /&gt;
				tmpSize /= 2;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	while (size &amp;lt; 4);  &lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Input signal length = &amp;quot; &amp;lt;&amp;lt; size &amp;lt;&amp;lt; endl; &lt;br /&gt;
	inputSignal = new complex&amp;lt;double&amp;gt;[size];   &lt;br /&gt;
	outputSignal = new complex&amp;lt;double&amp;gt;[size];&lt;br /&gt;
	DummyDataInitialization(inputSignal, size);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== инициализация входного сигнала ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void DummyDataInitialization(complex&amp;lt;double&amp;gt;* mas, int size)&lt;br /&gt;
{	&lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)&lt;br /&gt;
	{&lt;br /&gt;
		mas[i] = int(rand() % 100);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== процесс очистки памяти ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void ProcessTermination(complex&amp;lt;double&amp;gt;* &amp;amp;inputSignal, complex&amp;lt;double&amp;gt;* &amp;amp;outputSignal) &lt;br /&gt;
{ &lt;br /&gt;
	//очистка памяти&lt;br /&gt;
	delete[] inputSignal;   &lt;br /&gt;
	inputSignal = NULL;   &lt;br /&gt;
	delete[] outputSignal;   &lt;br /&gt;
	outputSignal = NULL; &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение операции &amp;quot;бабочка&amp;quot; ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void Butterfly(complex&amp;lt;double&amp;gt; *signal, complex&amp;lt;double&amp;gt; &amp;amp;u,int offset, int butterflySize)&lt;br /&gt;
{&lt;br /&gt;
	complex&amp;lt;double&amp;gt; tem = signal[offset + butterflySize] * u;&lt;br /&gt;
	signal[offset + butterflySize] = signal[offset] - tem;   &lt;br /&gt;
	signal[offset] += tem;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== выполнение БПФ ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void SerialFFTCalculation(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	int m = 0;   &lt;br /&gt;
	for (int tmp_size = size; tmp_size &amp;gt; 1; tmp_size /= 2, m++);&lt;br /&gt;
	for (int p = 0; p &amp;lt; m; p++)&lt;br /&gt;
	{&lt;br /&gt;
		int butterflyOffset = 1 &amp;lt;&amp;lt; (p + 1);&lt;br /&gt;
		int butterflySize = butterflyOffset &amp;gt;&amp;gt; 1;&lt;br /&gt;
		double coeff = M_PI / butterflySize;&lt;br /&gt;
&lt;br /&gt;
		for (int i = 0; i &amp;lt; size / butterflyOffset; i++)&lt;br /&gt;
			for (int j = 0; j &amp;lt; butterflySize; j++)&lt;br /&gt;
				Butterfly(signal,complex&amp;lt;double&amp;gt;(cos(-j*coeff),sin(-j*coeff)),j+i*butterflyOffset,butterflySize);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== функция вывода результата (для тестирования) ===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
void PrintSignal(complex&amp;lt;double&amp;gt; *signal, int size) &lt;br /&gt;
{&lt;br /&gt;
	cout &amp;lt;&amp;lt; &amp;quot;Result signal&amp;quot; &amp;lt;&amp;lt; endl;   &lt;br /&gt;
	for (int i = 0; i &amp;lt; size; i++)     &lt;br /&gt;
		cout &amp;lt;&amp;lt; signal[i] &amp;lt;&amp;lt; endl;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Рассмотренный алгоритм  Cooley – Tukey выполняется за &amp;lt;math&amp;gt;nlog_{2}n&amp;lt;/math&amp;gt; операций комплексного сложения и &amp;lt;math&amp;gt;\frac{n}{2}log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения. Таким образом последовательная сложность алгоритма является &#039;&#039;&#039;линейно-логарифмической&#039;&#039;&#039;.&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
[[file:Граф.jpg|center|thumb|800px|Рисунок 1. Алгоритм Cooley – Tukey для n=4, при предварительно сделанном бит-реверсировании]]&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
[[file:Парал.jpg|center|thumb|800px|Рисунок 2. Распараллеливание алгоритма Cooley – Tukey для n=8]]&lt;br /&gt;
Рисунок 2 получен из информационного графа, главной идеей которого является процедура деления множеств полученных на предыдущем шаге на два равных по мощности подмножества и применение к ним БПФ. То есть процесс вычисления распределяется между &amp;lt;math&amp;gt;\frac{n}{2}&amp;lt;/math&amp;gt; процессорами. И так как при последовательной реализации алгоритма было &amp;lt;math&amp;gt;\frac{n}{2}\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;n\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. Получается, что при параллельной реализации алгоритма мы будем иметь &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt; операций комплексного умножения и &amp;lt;math&amp;gt;2\log_{2}n&amp;lt;/math&amp;gt; комплексного сложения/вычитания. При этом, если используется функция HT(Hyper threading), то параллельная сложность быстрого преобразования Фурье (базовый алгоритм Кули-Тьюки) для векторов с длиной, равной степени двойки составляет &amp;lt;math&amp;gt;\log_{2}n&amp;lt;/math&amp;gt;.&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
*&#039;&#039;&#039;Вещественное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{R}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
*&#039;&#039;&#039;Комплексное ДФП&#039;&#039;&#039;&lt;br /&gt;
**входные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{a}=(a_{1},a_{2},...,a_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;a_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
**выходные данные: &lt;br /&gt;
***вектор &amp;lt;math&amp;gt;\overline{b}=(b_{1},b_{2},...,b_{N})&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;b_{i}\in \mathbb{C}&amp;lt;/math&amp;gt;&lt;br /&gt;
***размерность: &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; точек&lt;br /&gt;
== Свойства алгоритма ==&lt;br /&gt;
*Соотношение последовательной и параллельной сложности является &#039;&#039;линейным&#039;&#039;. Это хорошо видно из пункта [[#Ресурс параллелизма алгоритма|ресурс параллелизма алгоритма]].&lt;br /&gt;
*Вычислительная мощность алгоритма является &#039;&#039;логарифмической&#039;&#039;.&lt;br /&gt;
*Устойчивость. &lt;br /&gt;
**Рассмотрим вопрос об устойчивости поближе. Так как БПФ отличается от ДПФ простым лишь сокращением одинаковых операций, то вопрос устойчивости можно рассматривать на примере ДПФ. Так, к примеру, возьмем формулу ДПФ и добавим возмущение: &amp;lt;math&amp;gt; y_{p}=\sum_{k=0}^{n-1}(x_{k}+\varepsilon )e^{\frac{-2*\pi*k*p*i}{n}}, p=\overline{0,n-1}&amp;lt;/math&amp;gt;. После чего преобразуем ее: &amp;lt;math&amp;gt;y_{p}=\sum_{k=0}^{n-1}x_{k}e^{\frac{-2*\pi*k*p*i}{n}}+\varepsilon\sum_{k=0}^{n-1} e^{\frac{-2*\pi*k*p*i}{n}}&amp;lt;/math&amp;gt; и оценим вторую сумму, которая является геометрической прогрессией со знаменателем &amp;lt;math&amp;gt;q=e^{\frac{-2*\pi*p*i}{n}}&amp;lt;/math&amp;gt;. Таким образом оцениваемая сумма будет равняться: &amp;lt;math&amp;gt;\varepsilon\frac{1-e^{\frac{-2*\pi*p*i*(n-1))}{n}}}{1-e^{\frac{-2*\pi*p*i}{n}}}&amp;lt;/math&amp;gt;. При достаточно больших размерностях входного сигнала данное выражение сводится к: &amp;lt;math&amp;gt;\varepsilon (1-e^{-2*\pi*p*i})&amp;lt;/math&amp;gt;. В тригонометрической форме данного выражения: &amp;lt;math&amp;gt;\varepsilon (cos(\frac{2*\pi*p*(n-1)}{n})-i*sin(\frac{2*\pi*p*(n-1)}{n}))&amp;lt;/math&amp;gt; видно, что принимая во внимание целочисленность индексов &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; получается 0. Таким образом, мы можем говорить об устойчивости алгоритма в целом, и абсолютной устойчивости алгоритма в асимптотике.&lt;br /&gt;
*исходя из информационного графа, можно сделать вывод о сбалансированности алгоритма.&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:масш.jpg|center|thumb|1000px|Рисунок 3. Параллельная реализация алгоритма. Изменение эффективности(%)в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
Как описывалось выше, каждый процессор получает достаточно низкий объем работы, что приводит к неэффективному распараллеливанию алгоритма (высокие затраты на передачу данных между процессорами по сравнению с вычислениями на них). Поэтому необходимо находить оптимальное количество процессоров для каждой отдельно взятой задачи. В данном примере, при размерности входного сигнала менее 262144, оптимальное количество процессоров составляет 1. При размерности 262144-4194304 оптимум достигался при 8 процессорах. И при размерности более 4194304- 16 процессоров. Вычисления проводились на суперкомпьютере «Ломоносов». &lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
Самой распространенной и известной реализацией дискретного БПФ является библиотека [http://www.fftw.org/ FFTW], разработанная Маттео Фриго и Стивеном Г. Джонсоном в Массачусетском технологическом институте. Данная библиотека существует для языков C и FORTRAN и позволяет производить вычисления в одном или нескольких измерениях. Входной сигнал может быть как действительным так и комплексным, а его размерность не обязательно должна быть степенью двойки. На данный момент последняя версия FFTW 3.3.5. Другие особенности библиотеки:&lt;br /&gt;
*Скорость. (Поддержка SSE / SSE2 / Altivec, начиная с версии 3.0. Версия 3.3.1 поддерживает AVX и ARM Neon.)&lt;br /&gt;
*Параллельные вычисления (OpenMP, MPI).&lt;br /&gt;
*[http://www.fftw.org/fftw3_doc/ Документация] в форматах HTML, PDF.&lt;br /&gt;
*Данное программное обеспечение является свободным и распространяется по лицензии GNU (General Public License).&lt;br /&gt;
Здесь приводится пример реализации прямого БПФ для комплексных данных входного сигнала на языке C++. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;С++&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;fftw3-mpi.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
# include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/stat.h&amp;gt;&lt;br /&gt;
#include &amp;lt;fcntl.h&amp;gt;&lt;br /&gt;
# include &amp;lt;time.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char **argv)&lt;br /&gt;
{&lt;br /&gt;
	const ptrdiff_t N0 = 4194304;&lt;br /&gt;
	fftw_plan planForw;&lt;br /&gt;
	fftw_complex *data, *dataOut;&lt;br /&gt;
	ptrdiff_t alloc_local, local_ni, local_i_start, i, j, local_no, local_o_start;&lt;br /&gt;
	int index, size;&lt;br /&gt;
	double startwtime, endwtime;&lt;br /&gt;
	MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
	fftw_mpi_init();&lt;br /&gt;
	MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;index);&lt;br /&gt;
	MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);&lt;br /&gt;
&lt;br /&gt;
	/* get local data size and allocate */&lt;br /&gt;
	alloc_local = fftw_mpi_local_size_1d(N0, MPI_COMM_WORLD, FFTW_FORWARD, FFTW_ESTIMATE,&lt;br /&gt;
		&amp;amp;local_ni, &amp;amp;local_i_start, &amp;amp;local_no, &amp;amp;local_o_start);&lt;br /&gt;
	data = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	dataOut = fftw_alloc_complex(alloc_local);&lt;br /&gt;
	/* create plan  */&lt;br /&gt;
	planForw = fftw_mpi_plan_dft_1d(N0, data, dataOut, MPI_COMM_WORLD,&lt;br /&gt;
		FFTW_FORWARD, FFTW_ESTIMATE);&lt;br /&gt;
	/* initialize data to some function my_function(x,y) */&lt;br /&gt;
	for (i = 0; i &amp;lt; local_ni; ++i)&lt;br /&gt;
	{&lt;br /&gt;
		data[i][0] = rand() / (double)RAND_MAX;&lt;br /&gt;
		data[i][1] = rand() / (double)RAND_MAX;&lt;br /&gt;
	}&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		startwtime = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_execute(planForw);&lt;br /&gt;
	if (index == 0){&lt;br /&gt;
		endwtime = MPI_Wtime();&lt;br /&gt;
		printf(&amp;quot;wall clock time = %f\n&amp;quot;,&lt;br /&gt;
			endwtime - startwtime);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	fftw_destroy_plan(planForw);&lt;br /&gt;
	MPI_Finalize();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
[http://www.fftw.org/download.html Скачав] библиотеку и установив ее на рабочий компьютер, можно поэкспериментировать, используя вышеописанный исходный код. При этом, на стадии компиляции необходимо указывать директорию, где находятся заголовочные файлы и директорию, где находятся библиотечные файлы. Пример: mpicxx name.cpp -I/path/to/include/files -L/path/to/lib/files  -lfftw3_mpi -lfftw3 -lm. Для версии 2.1.4 описание на русском языке приводится [https://parallel.ru/cluster/fftw.html здесь].&lt;br /&gt;
&lt;br /&gt;
Остальные реализации:&lt;br /&gt;
*[http://www.gnu.org/software/gsl/ GSL - GNU Scientific Library]&lt;br /&gt;
*[https://developer.nvidia.com/cufft cuFFT]&lt;br /&gt;
*[http://www-03.ibm.com/systems/power/software/essl/ Engineering and Scientific Subroutine Library (ESSL) and Parallel ESSL]&lt;br /&gt;
=Литература=&lt;br /&gt;
[1] Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний &lt;br /&gt;
&lt;br /&gt;
[2] Г. Нуссбаумер. Быстрое преобразование Фурье и алгоритмы вычисления сверток &lt;br /&gt;
&lt;br /&gt;
[3] А.О. Лапаев Параллельное вычисление дискретного преобразования Фурье полинома в простом поле&lt;/div&gt;</summary>
		<author><name>IgorS</name></author>
	</entry>
</feed>