Быстрее мощность матрицы, чем numpy?

Мне нужно было вычислить Q ^ N для множества различных значений N (от 1 до 10000), а Numpy был слишком медленным.

Я спросил math.stackexchange.com, если бы мог избежать вычисления Q ^ N для моей конкретной потребности, и кто-то ответил мне, что вычисление Q ^ N должно быть вполне быстро используя метод P D^N P^-1.

Итак, в основном, вместо того, чтобы делать:

import numpy as np
from numpy import linalg as LA
...
LA.matrix_power(m, N)

Я пробовал:

diag, P = LA.eig(m)
DN = np.diag(diag**N)
P1 = LA.inv(P)

P*DN*P1

И я получаю ту же матрицу, что и результат (в одном примере)

В более сложной матрице Q:

% timeit.Timer('Q**10000', setup=setup).repeat(2, 100)
[5.87254786491394, 5.863131046295166]

% timeit.Timer('diag, P = linalg.eig(Q); DN=np.diag(diag**10000);P1=linalg.inv(P); P*DN*P1', setup=setup).repeat(2, 100)
[2.0032401084899902, 2.018735885620117]

И что касается моей первоначальной проблемы, второй метод позволяет мне вычислить P, diag and P1 только один раз и использовать его тысячи раз. Это в 8 раз быстрее, используя этот метод.

Мои вопросы:

  • В этом случае невозможно использовать этот последний метод для вычисления Q ^ N?
  • Можно ли использовать его в моем случае (матрица Q, как указано здесь)?
  • Есть ли в numpy функция, которая уже делает это?

Edit:

  • Похоже, что для другой матрицы P не обратим. Поэтому я добавляю новый вопрос: как я могу изменить матрицу P, чтобы она стала обратимой, но результирующая матрица не слишком изменена? Я имею в виду, что это нормально, если значения близки к реальному результату, закрываясь, я имею в виду ~ 0,0001.

Ответы

Ответ 1

Я частично отвечаю на свой вопрос:

В соответствии с исходным кодом, я думаю, что Numpy использует Exponentiation by Squaring:

# binary decomposition to reduce the number of Matrix
# multiplications for n > 3.
beta = binary_repr(n)
Z, q, t = M, 0, len(beta)
while beta[t-q-1] == '0':
    Z = N.dot(Z, Z)
    q += 1
result = Z
for k in range(q+1, t):
    Z = N.dot(Z, Z)
    if beta[t-k-1] == '1':
        result = N.dot(result, Z)
return result

Что в моем случае медленнее, когда n велико, чем вычисление собственных значений и собственных векторов и вычисление M ^ N как равное P D ^ N P ^ -1.

Теперь, касаясь моих вопросов:

В этом случае невозможно использовать этот последний метод для вычисления Q ^ N?

Когда некоторые собственные значения равны, невозможно будет инвертировать P. кто-то предложил сделать это в Numpy в отслеживании проблем. Ответ был: "Ваш подход действителен только для не дефектных плотных матриц".

Можно ли использовать его в моем случае (матрица Q, как указано здесь)?

Не всегда, у меня может быть несколько одинаковых собственных значений.

Есть ли в numpy функция, которая уже делает это?

Я думаю, что это в SciPy: https://github.com/scipy/scipy/blob/v0.12.0/scipy/linalg/matfuncs.py#L57

Таким образом, мы также можем сделать это:

LA.expm(n*LA.logm(m))

для вычисления m ^ n.

Как я могу изменить матрицу P, чтобы она стала обратимой, но результирующая матрица не слишком изменена? Я имею в виду, это нормально, если значения близки к реальному результату, по закрытию я имею в виду ~ 0,0001.

Я не могу просто добавить значение epsilon, потому что метод декомпозиции разумен, когда значения слишком близки. Я уверен, что это может привести к непредсказуемым ошибкам.

Ответ 2

Вы уже поняли, что ваши собственные значения будут (0, a, b, c, ..., 1). Позвольте мне переименовать ваши параметры, так что собственные значения (0, e1, e2, e3, ..., 1). Чтобы узнать собственный вектор (v0, v1, v2, ..., v(n-1)), соответствующий собственному значению ej, вам необходимо решить систему уравнений:

v1                    = v0*ej
v1*e1 + v2*(1-e1)     = v1*ej
v2*e2 + v3*(1-e2)     = v2*ej
...
vj*ej + v(j+1)*(1-ej) = vj*ej
...
v(n-1)                = v(n-1)*ej

Более или менее понятно, что если все ваши ei различны, а ни один не равен 0 или 1, тогда решение всегда определено всегда, а при работе с ej результат собственный вектор имеет первые компоненты j, отличные от нуля, а остальные равны нулю. Это гарантирует, что ни один собственный вектор не является линейной комбинацией остальных, и, следовательно, матрица собственного вектора обратим.

Проблема возникает, когда некоторые из ваших ei либо 0, либо 1, либо повторяются. Я не смог придумать доказательство этого, но, экспериментируя со следующим кодом, кажется, что вы должны только беспокоиться, если любые два из ваших ei равны и отличаются от 1:

>>> def make_mat(values):
...     n = len(values) + 2
...     main_diag = np.concatenate(([0], values, [1]))
...     up_diag = 1 - np.concatenate(([0], values))
...     return np.diag(main_diag) + np.diag(up_diag, k=1)
>>> make_mat([4,5,6])
array([[ 0,  1,  0,  0,  0],
       [ 0,  4, -3,  0,  0],
       [ 0,  0,  5, -4,  0],
       [ 0,  0,  0,  6, -5],
       [ 0,  0,  0,  0,  1]])
>>> a, b = np.linalg.eig(make_mat([4,5,6]))
>>> a
array([ 0.,  4.,  5.,  6.,  1.])
>>> b
array([[ 1.        ,  0.24253563, -0.18641093,  0.13608276,  0.4472136 ],
       [ 0.        ,  0.9701425 , -0.93205465,  0.81649658,  0.4472136 ],
       [ 0.        ,  0.        ,  0.31068488, -0.54433105,  0.4472136 ],
       [ 0.        ,  0.        ,  0.        ,  0.13608276,  0.4472136 ],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.4472136 ]])

И теперь для некоторых тестовых случаев:

>>> a, b = np.linalg.eig(make_mat([1,0,3])) # having a 0 or 1 is OK
>>> b
array([[ 1.        ,  0.70710678,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.70710678,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  1.        ,  0.31622777,  0.57735027],
       [ 0.        ,  0.        ,  0.        ,  0.9486833 ,  0.57735027],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.57735027]])
>>> a, b = np.linalg.eig(make_mat([1,1,3])) # repeating 1 is OK
>>> b
array([[ 1.        ,  0.70710678,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.70710678,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  1.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  1.        ,  0.70710678],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.70710678]])
>>> a, b = np.linalg.eig(make_mat([0,0,3])) # repeating 0 is not OK
>>> np.round(b, 3)
array([[ 1.   , -1.   ,  1.   ,  0.035,  0.447],
       [ 0.   ,  0.   ,  0.   ,  0.105,  0.447],
       [ 0.   ,  0.   ,  0.   ,  0.314,  0.447],
       [ 0.   ,  0.   ,  0.   ,  0.943,  0.447],
       [ 0.   ,  0.   ,  0.   ,  0.   ,  0.447]])
>>> a, b = np.linalg.eig(make_mat([2,3,3])) # repeating other values are not OK
>>> np.round(b, 3)
array([[ 1.   ,  0.447, -0.229, -0.229,  0.447],
       [ 0.   ,  0.894, -0.688, -0.688,  0.447],
       [ 0.   ,  0.   ,  0.688,  0.688,  0.447],
       [ 0.   ,  0.   ,  0.   ,  0.   ,  0.447],
       [ 0.   ,  0.   ,  0.   ,  0.   ,  0.447]])