Главная
страница 1
Параллельный алгоритм поиска собственных чисел для симметричной тридиагональной матрицы

Введение

Во многих практических задачах, таких как имитационное моделирование, генетические алгоритмы обучения и т. д. часто требуется получить набор случайных векторов с нормальным распределением, заданным ковариационной матрицей. Чтобы сгенерировать случайный вектор, требуется найти собственные числа и собственные векторы матрицы ковариации.

Обобщенная задача поиска собственных чисел и векторов для матрицы может быть решена с помощью SVD (singular value decomposition) разложения. Однако вычислительная сложность SVD - O(n3), что может быть серьезной проблемой при решении больших задач.

Специализированные алгоритмы поиска собственных чисел и векторов имеют сложность O(n2). Эти алгоритмы принимают на вход симметричную тридиагональную матрицу. Тридиагональная матрица – матрица в которой заданны главная диагональ и по одной диагонали выше и ниже главной, остальные элементы равны нулю. Симметричная трилиагональная матрица размера nxn может быть описана двумя векторами: вектором главной диагонали – d, длинной n, и вектором диагонали под/над главной – e, длинной n1.



Алгоритм

Пусть A – симметричная тридиагональная матрица размера n×n c диагональю d0, …,dn-1 и наддиагональю e0,…,en-2, а z0,…,zm-1 – собственные числа матрицы. Известно, что есть функция count(x), определенная ниже, которая возвращает количество собственных чисел, меньше x. Текст функции приведен в алгоритме 1.

count = 0;

diff = d0 – x;

for i = 1 to n – 1

diff = di-x-;

if diff < 0 then count = count + 1;

end for
Алгоритм 1 - функция count(x), которая возвращает количество собственных чисел, меньше x.

Таким образом, количество собственных чисел в интервале [x1; x2) можно легко вычислить: count(x2) – count(x1). Это свойство позволяет использовать любой алгоритм поиска, например метод дихотомии, для поиска собственных чисел на заданном отрезке. Такой алгоритм поиска будет полагаться на тот факт, что функция count(x) монотонна. Но реализация count(x) для конкретной машины с арифметикой с плавающей точкой floatCount(x) не обязательно будет монотонной [1]. Так можно будет найти такие x1 < x2, что floatCount(x1) > floatCount(x2), что, очевидно, неверно. Ниже будут приведены приемы, позволяющие избежать получения денормализованных чисел, при операциях с плавающей точкой.

Если требуется найти все собственные числа матрицы, то начальный отрезок для поиска [x1; x2) можно рассчитать по теореме Гершгорина. Пусть Ri – сумма абсолютных значений недиагональных элементов i строки матрицы A, то D(aii, Ri) будет диском Гершгорина с центром aii и радиусом Ri. Теорема Гершгорина гласит, что каждое собственное число матрицы A находится внутри хотя бы одного из дисков Гершгорина. Доказательство может быть найдено в [2]. Для вычисления x1 и x2 можно использовать алгоритм 2.

x1 = d0;

x2 = d0;

prev = 0;

for j = 0 to n - 2

x1 = min(x1, dj - prev - |ej|);

x2 = max(x2, dj + prev + |ej|);

prev = |ej|;

endfor


x1 = min(x1, dn-1 - prev);

x2 = max(x2, dn-1 + prev);

Алгоритм 2 – Вычисление интервала Гершгорина [x1; x2), содержащего все собственные числа матрицы

Ниже приведен псевдокод алгоритма поиска m собственных чисел. Алгоритм 3 оптимизирован для вычислений на GPU.

Алгоритм 3 использует служебную функцию getNumEigensIn для вычисления количества собственных чисел, находящихся внутри интервала [low, up). Из-за особенностей арифметики с плавающей точкой, можно найти такие low и up, что floatCount(up) - floatCount(low) будет меньше 0, что некорректно в данном контексте. Чтобы избежать такой ситуации, добавлено дополнительное действие max(0, count).

Процедура refineIntervals выполняет одну итерацию деления интервалов. Предполагается, что refineIntervals выполняется параллельно на m потоках, где m – количество собственных чисел, которые требуется найти. Общее количество собственных чисел для тридиагональной матрицы можно найти, если найти разность count(gu) – count(gl), где gu и gl – границы интервала Гершгорина (алгоритм 2).

На вход функция refineIntervals получает параметра: inIL0..m-1, inIU0..m-1, outIL0..m-1, outIU0..m-1.Где inILi (input interval lower) – нижняя граница i входного интервала, inIUi (input interval upper) – верхняя граница i входного интервала, outILi (output interval lower) – нижняя граница i выходного интервала, outIUi (output interval upper) – верхняя граница i выходного интервала.

Предполагается, что getThreadNumber() – системная функция, возвращающая порядковый номер вычислительного потока.

Первым шагом вычислительный поток отыскивает индекс i интервала, содержащего id-ое собственное число. Стоит отметить, что цикл «перешагивает» через интервалы, не содержащие собственных чисел.

Вторым шагом refineIntervals выполняет деление интервала. Функция isNarrow возвращает истину, если интервал достаточно мал и не подлежит дальнейшему делению. Ниже будет обсуждаться численная стабильность реализации isNarrow.

Если интервал содержит одно собственное число, то он делится пополам. Вычисляется количество собственных чисел лежащих в левом подинтервале [inILi, middle). Если оно больше 0, то берется нижняя половина, иначе – верхняя.

Если интервал содержит более одного собственного числа, то он делится на n равных частей. Причем, каждый вычислительный поток берет только splitN-ую часть из n частей. Для улучшения численной устойчивости реализации алгоритма, важно выполнять именно такие действия при разбиении интервала.

function getNumEigensIn(low, up)

count = floatCount(up) - floatCount(low);

getNumEigensIn = max(0, count);

end function


procedure refineIntervals(inIL, inIU, outIL, outIU)

id = getThreadNumber();


// шаг 1: найти индекс входного интервала

i = 0;


splitN = id;

while splitN >= getNumEigensIn(inILi, inIUi)

splitN = splitN - getNumEigensIn(inILi, inIUi);

i = i + 1;

end while
// 2) обработать входной интервал

if isNarrow(inILi, inIUi) then

// если интервал достаточно мал,

// оставить его без изменений

outILid = inILi;

outIUid = inIUi;

else

n = getNumEigensIn(inILi, inIUi);



// разделить интервал

if n = 1 then

// деление интервала пополам

middle = 0.5 * (inILi + inIUi);

if getNumEigensIn(inILi, middle) > 0 then

// взять нижнюю половину интервала

outILid = inILi;

outIUid = middle;

else

// взять верхнюю половину интервала



outILid = middle;

outIUid = inIUi;

end if

else


// поледить интервал на n частей и взять splitN часть

width = abs(inIUi - inILi) / n;

outILid = inILi + splitN * width;

outIUid = outILid + width;

end if

end if


end procedure
Алгоритм 3 – Параллельный алгоритм разделения интервалов

Если проанализировать шаг 1 алгоритма 3, то станет ясно, что на первой итерации refineIntervals достаточно чтобы был известен один только начальный интервал, при условии, что он содержит все искомые собственные числа. При поиске всех собственных чисел достаточно инициализировать inIL0 и inIU0 верхней и нижней границами интервала Гершгорина.

Полностью алгоритм поиска всех собственных чисел поиска всех собственных чисел приведен в алгоритме 4.

(inIL0, inIU0) = getGersgorinInterval();

for i = 1 to maxIterations

parallel for j = 1 to m

refineIntervals(inIL, inIU, outIL, outIU);

end parallel for

if isAllNarrow(outIL, outIU) then break;

swap(inIL, outIL);

swap(inIU, outIU);

end for


Алгоритм 4 – Алгоритм итеративного приближения интервалов к собственным числам матрицы.

Каждая итерация цикла parallel for выполняется отдельным вычислительным потоком. Функция isAllNarrow возвращает истину, если isNarrow истина для всех inIUi, outIUi, i=0..m-1. Если на каждом шаге ширина интервала уменьшается в 2 раза, то максимальное число итераций maxIterations, за которое любой интервал станет достаточно малым будет равно . Где w0 – начальная ширина интервала (inIU0 - inIL0 в алгоритме 4), а wmin – минимальная ширина интервала.



Численная устойчивость

Реализация floatCount отличается от алгоритма count (алгоритм 1), тем, что может терять точность и/или выдавать неверные результаты из-за погрешностей или особых случаев арифметики с плавающей точкой. Все это может привести к тому, что floatCount может не быть монотонной. Проблемное место – вычисление следующего значения diff.

diff = d0 – x;

diff = di-x-

При малых значениях diif может возникнуть переполнение при делении, которое приведет к тому, что реализация floatCount перестанет быть монотонной. Введем число pivmin – для данной арифметики с плавающей точкой, минимальное положительное число, такое что не равно бесконечности (не вызывает переполнения) для всех i=0,…,n-2. Тогда численно стабильная версия floatCount1 может быть реализована с помощью алгоритма 5.

function floatCount1(x, pivmin)

diff = d0 - x;

if |diff| < pivmin then diff = -pivmin;

if diff <= 0 then floatCount1 = 1 else floatCount1 = 0;

for i = 1 to n - 1

diff = di - x - / diff;

if |diff| < pivmin then diff = -pivmin;

if diff <= 0 then floatCount1 = floatCount1 + 1;

end for


end function

Алгоритм 5 – численно устойчивая реализация функции floatCount.

Если в предыдущей реализации floatCount при делении на diff меньше pivmin в результате получалась бесконечность (специальное состояние вещественного числа – INF из стандарта IEEE 754 [3]), то следующая операция /diff даст 0. При делении на бесконечность всегда получается 0. Это приведет к неточности.

Реализация floatCount1 лишена такого недостатка, так как в ней diff не может быть по модулю меньше pivmin. Следовательно, операция /diff никогда не приведет к переполнению.

Отдельного упоминания заслуживает функция isNarrow, которая должна возвращать истину, если интервал достаточно узок. В теории, алгоритм бисекции интервалов должен давать сколько угодно узкий интервал. А поскольку значение собственного числа является средним арифметическим от верней и нижней границ интервала, это должно позволить находить собственные числа о сколько угодно высокой точностью. Но на практике в арифметике с плавающей точкой может быть представлено только несколько цифр. Для чисел одинарной точности количество цифр – 6-7. Это значит, что например числа в окрестности единицы могут быть представлены с точностью до 6 знака после запятой, а в окрестности 10000 – только с точностью до 3 знака. Это необходимо учитывать при сравнении чисел с плавающей точкой, а также при определении практической минимальной возможной ширины интервала, после которой продолжать разбиение интервала бессмысленно.

Введем минимальное число eps, такое что 1 + eps <> 1. Для вещественных чисел с одинарной точностью стандарта IEEE 754 это значение равно 1.192092896e-07, для чисел с двойной точностью - 2.2204460492503131e-016. В арифметике с плавающей точкой если модуль разности между числом и единицей меньше eps, считается что число равно единице. Помня, что точность представления с плавающей точкой относительна, можно обобщить это утверждение: если |a-b|

Однако пользователь может желать указать также минимальную ширину интервала, после которой нет смысла производить деление интервала. Назовем такую ширину абсолютной погрешностью. Тогда функцию isNarrow можно реализовать с помощью алгоритма 6, где a и b соответственно нижняя и верхняя границы интервала, absTol – абсолютная минимальная ширина интервала, заданная пользователем, а relTol = eps для машин с IEEE 754 подобной арифметикой.

function isNarrow(a, b, absTol, relTol)

width = |a - b|;

norm = max(|a|, |b|);

minWidth = max(absTol, relTol * norm);

isNarrow = (width < minWidth);

end function

Алгоритм 6 – реализация функции isNarrow, возвращающей истину, если дальнейшее деление интервала [a, b) не оправдано.



Реализация поиска собственных чисел в LAPACK

В библиотеке алгоритмов LAPACK (Linear Algebra PACKage – пакет линейной алгебры) [4] также используется реализация алгоритма поиска собственных чисел методом последовательного разбиения интервала Гершгорина. Для этого в LAPACK существует функция *STEBZ.

*STEBZ вычисляет собственные числа симметричной тридиагональной матрицы T. Пользователь может вычислить с ее помощью все собственные числа, все собственные числа в полуоткрытом интервале (VL, VU], или собственные числа с номерами с IL по IU. Чтобы избежать переполнения, матрица должна быть отмасштабирована так, чтобы наибольший по модулю элемент не превышал , и для большей точности, он не должен быть значительно меньше этого значения.

В LAPACK присутствуют различные реализации *STEBZ, различающиеся первой буквой названия, которая определяет тип используемых для вычислений переменных: вещественных одинарной (SSTEBZ) или двойной (DSTEBZ) точностью.

Реализация из AMD APP SDK (статья?)

Моя реализация

Сравнение

Выводы


Источники

  1. J. Demmel, I. Dhillon, and H. Ren, On The Correctness Of Some Bisection-Like Parallel Eigenvalue Algorithms In Floating Point Arithmetic, Trans. Num. Anal. (ETNA), 3, 1996.

  2. http://en.wikipedia.org/wiki/Gershgorin_circle_theorem

  3. IEEE Standard for Floating-Point Arithmetic http://ieeexplore.ieee.org/xpl/mostRecentIssue.jsp?punumber=4610933

  4. LAPACK http://www.netlib.org/lapack/


Смотрите также:
Параллельный алгоритм поиска собственных чисел для симметричной тридиагональной матрицы
117.68kb.
1 стр.
Решение производим в matlab. Зададим матрицу S=[l 3 1 0]. Функция expm(S) возвращает значение экспоненты от матрицы: a = expm(S). Таким образом
21.38kb.
1 стр.
Приведение матрицы к диагональному виду. Каноническое разложение матрицы
38.46kb.
1 стр.
Lu-разложение матрицы
25.9kb.
1 стр.
Линейная алгебра
257.57kb.
3 стр.
Задача 3 n + 1 [Вальядолид, 100]. Рассмотрим следующий алгоритм
417.5kb.
4 стр.
Нечеткие последовательности, нечеткие прямоугольные матрицы, нечеткие функции и операции над ними
18.33kb.
1 стр.
Генераторы псевдослучайных чисел и их применимость для расчетов монте-карло
44.25kb.
1 стр.
Матрицы и определители Числовые матрицы и действия над ними
184.57kb.
1 стр.
Урок по теме «Состав чисел»
31.67kb.
1 стр.
Алгоритм поиска пример
10.55kb.
1 стр.
Технология подготовки и проведения ток-шоу
35.7kb.
1 стр.