Можно ли использовать linalg.matrix_power numpy по модулю, чтобы элементы не превышали определенное значение?
Numpy матричная мощность / экспонента с модулем?
Ответы (4)
Чтобы предотвратить переполнение, вы можете использовать тот факт, что вы получите тот же результат, если сначала возьмете модуль каждого из ваших входных чисел; по факту:
(M**k) mod p = ([M mod p]**k) mod p,
для матрицы M
. Это следует из следующих двух фундаментальных тождеств, действительных для целых чисел x
и y
:
(x+y) mod p = ([x mod p]+[y mod p]) mod p # All additions can be done on numbers *modulo p*
(x*y) mod p = ([x mod p]*[y mod p]) mod p # All multiplications can be done on numbers *modulo p*
Те же тождества справедливы и для матриц, поскольку сложение и умножение матриц можно выразить через скалярное сложение и умножение. При этом вы возводите в степень только небольшие числа (n mod p обычно намного меньше, чем n) и с гораздо меньшей вероятностью получите переполнение. Поэтому в NumPy вы просто делаете
((arr % p)**k) % p
чтобы получить (arr**k) mod p
.
Если этого все еще недостаточно (т. е. если есть риск того, что [n mod p]**k
вызовет переполнение, несмотря на то, что n mod p
мало), вы можете разбить возведение в степень на несколько возведений в степень. Фундаментальные тождества выше дают
(n**[a+b]) mod p = ([{n mod p}**a mod p] * [{n mod p}**b mod p]) mod p
а также
(n**[a*b]) mod p = ([n mod p]**a mod p)**b mod p.
Таким образом, вы можете разбить силу k
на a+b+…
или a*b*…
или любую их комбинацию. Приведенные выше тождества позволяют выполнять только возведение в степень небольших чисел с помощью малых чисел, что значительно снижает риск целочисленных переполнений.
Использование реализации из Numpy:
https://github.com/numpy/numpy/blob/master/numpy/matrixlib/defmatrix.py#L98
Я адаптировал его, добавив термин по модулю. ОДНАКО существует ошибка, заключающаяся в том, что при переполнении не возникает OverflowError
или любое другое исключение. С этого момента решение будет неверным. Отчет об ошибке находится здесь.
Вот код. Используйте с осторожностью:
from numpy.core.numeric import concatenate, isscalar, binary_repr, identity, asanyarray, dot
from numpy.core.numerictypes import issubdtype
def matrix_power(M, n, mod_val):
# Implementation shadows numpy's matrix_power, but with modulo included
M = asanyarray(M)
if len(M.shape) != 2 or M.shape[0] != M.shape[1]:
raise ValueError("input must be a square array")
if not issubdtype(type(n), int):
raise TypeError("exponent must be an integer")
from numpy.linalg import inv
if n==0:
M = M.copy()
M[:] = identity(M.shape[0])
return M
elif n<0:
M = inv(M)
n *= -1
result = M % mod_val
if n <= 3:
for _ in range(n-1):
result = dot(result, M) % mod_val
return result
# binary decompositon 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 = dot(Z, Z) % mod_val
q += 1
result = Z
for k in range(q+1, t):
Z = dot(Z, Z) % mod_val
if beta[t-k-1] == '1':
result = dot(result, Z) % mod_val
return result % mod_val
Что не так с очевидным подходом?
E.g.
import numpy as np
x = np.arange(100).reshape(10,10)
y = np.linalg.matrix_power(x, 2) % 50
У меня были проблемы с переполнением во всех предыдущих решениях, поэтому мне пришлось написать алгоритм, который учитывает переполнение после каждого целочисленного умножения. Вот как я это сделал:
def matrix_power_mod(x, n, modulus):
x = np.asanyarray(x)
if len(x.shape) != 2:
raise ValueError("input must be a matrix")
if x.shape[0] != x.shape[1]:
raise ValueError("input must be a square matrix")
if not isinstance(n, int):
raise ValueError("power must be an integer")
if n < 0:
x = np.linalg.inv(x)
n = -n
if n == 0:
return np.identity(x.shape[0], dtype=x.dtype)
y = None
while n > 1:
if n % 2 == 1:
y = _matrix_mul_mod_opt(x, y, modulus=modulus)
x = _matrix_mul_mod(x, x, modulus=modulus)
n = n // 2
return _matrix_mul_mod_opt(x, y, modulus=modulus)
def matrix_mul_mod(a, b, modulus):
if len(a.shape) != 2:
raise ValueError("input a must be a matrix")
if len(b.shape) != 2:
raise ValueError("input b must be a matrix")
if a.shape[1] != a.shape[0]:
raise ValueError("input a and b must have compatible shape for multiplication")
return _matrix_mul_mod(a, b, modulus=modulus)
def _matrix_mul_mod_opt(a, b, modulus):
if b is None:
return a
return _matrix_mul_mod(a, b, modulus=modulus)
def _matrix_mul_mod(a, b, modulus):
r = np.zeros((a.shape[0], b.shape[1]), dtype=a.dtype)
bT = b.T
for rowindex in range(r.shape[0]):
x = (a[rowindex, :] * bT) % modulus
x = np.sum(x, 1) % modulus
r[rowindex, :] = x
return r
pow(x, y, z=None, /)
, которая равнаEquivalent to x**y (with two arguments) or x**y % z (with three arguments)
- person Tomasz Gandor   schedule 30.11.2019