Выясните, совпадают ли две симметричные матрицы с точностью до перестановки строк/столбцов.

У меня есть две симметричные (совпадение элементов) матрицы A и B, и я хочу выяснить, описывают ли они одно и то же совпадение, только с переставленными метками строк/столбцов. (Та же самая перестановка должна применяться к строкам и столбцам, чтобы сохранить свойство симметрии/совместности)

Например, в моем тесте эти две матрицы должны быть равны:

a = np.array([
    #1 #2 #3 #4 #5 #6 #7
    [0, 1, 1, 0, 0, 0, 1], #1
    [1, 0, 1, 2, 1, 1, 2], #2
    [1, 1, 0, 0, 0, 0, 1], #3
    [0, 2, 0, 0, 4, 0, 4], #4
    [0, 1, 0, 4, 0, 1, 2], #5
    [0, 1, 0, 0, 1, 0, 0], #6
    [1, 2, 1, 4, 2, 0, 0]  #7
])
b = np.array([
    #5 #7 #1,3#3,1#2 #4 #6
    [0, 2, 0, 0, 1, 4, 1], #5
    [2, 0, 1, 1, 2, 4, 0], #7
    [0, 1, 0, 1, 1, 0, 0], #1,3 could be either
    [0, 1, 1, 0, 1, 0, 0], #1,3 could be either
    [1, 2, 1, 1, 0, 2, 1], #2
    [4, 4, 0, 0, 2, 0, 0], #4
    [1, 0, 0, 0, 1, 0, 0]  #6
])

В настоящее время я проверяю, совпадают ли собственные значения, используя numpy.linalg.eigvals (я даже не уверен, что это достаточное условие), но я хотел бы найти тест, который не требует числовой точности, поскольку здесь я имею дело с целыми числами.


person C. Yduqoli    schedule 29.10.2018    source источник
comment
Эта проблема эквивалентна изоморфизму графов: точные решения, скорее всего, будут медленными. См., например. этот вопрос   -  person Maxim    schedule 29.10.2018
comment
Мне было интересно, как вы создали b, не зная индексов [5, 7, 1, 3, 2, 4, 6] в первую очередь.   -  person Andreas K.    schedule 29.10.2018
comment
Я рассчитываю совместное появление объектов на основе списка списков объектов. Этим объектам назначаются случайные индексы (вне моего контроля) до того, как будет создана матрица совпадений. Таким образом, каждый раз создается новая матрица сотрудничества. В моем примере я использовал две из этих матриц и вручную присвоил индексы второй матрице.   -  person C. Yduqoli    schedule 30.10.2018
comment
Я бы рекомендовал ознакомиться с проблемой изоморфизма графов и проверить, подходит ли вам конкретный вариант график — это решенная задача. Если нет, то, вероятно, вас ждет грубая сила.   -  person Daniel F    schedule 30.10.2018
comment
Если во многих случаях ваши матрицы не будут эквивалентны, возможно, стоит сначала выполнить тест, который скажет вам, если они не эквивалентны, но не скажет вам, являются ли они таковыми. Например, вы можете для каждой матрицы вычислить мультимножество каждой строки (например, первая строка будет {(4,0), (3,1)}), а затем сформировать мультимножество мультимножеств строк. Если эти два мультимножества (один для a, один для b) не равны, то матрицы не эквивалентны.   -  person dmuir    schedule 30.10.2018


Ответы (3)


Вот векторизованное решение, основанное на sorting и использующее searchsorted -

import pandas as pd

# Sort rows for a and b
aS = np.sort(a,axis=1)
bS = np.sort(b,axis=1)

# Scale down each row to a scalar each
scale = np.r_[(np.maximum(aS.max(0),bS.max(0))+1)[::-1].cumprod()[::-1][1:],1]
aS1D = aS.dot(scale)
bS1D = bS.dot(scale)

# Use searchsorted to get the correspondence on indexing
sidx = aS1D.argsort()
searchsorted_idx = np.searchsorted(aS1D,bS1D,sorter=sidx)
searchsorted_idx[searchsorted_idx==len(aS1D)] = len(aS1D)-1
df = pd.DataFrame({'A':searchsorted_idx})
new_order = sidx[df.groupby('A').cumcount().values+searchsorted_idx]
# new_order is the permuted order, i.e. [5, 7, 1, 3, 2, 4, 6]

# Finally index into a with the new_order and compare against b
out = np.array_equal(a[new_order[:,None], new_order],b)
person Divakar    schedule 29.10.2018
comment
Хороший, но намного медленнее, чем решение с собственными значениями. - person Andreas K.; 29.10.2018
comment
@AndyK На каком размере вы его тестируете? - person Divakar; 29.10.2018
comment
Это не кажется правильным. Я попробовал a = scipy.linalg.toeplitz(np.arange(8)) и b какую-то перетасованную версию этого, и все время получаю False. - person Paul Panzer; 29.10.2018
comment
@AndyK Я почти уверен, что собственных значений недостаточно. например, [[4 0] [0 0]] и [[2 2] [2 2]] имеют одинаковые собственные значения, но, очевидно, не могут быть перемешаны друг с другом. - person Paul Panzer; 29.10.2018
comment
Как насчет np.array([[12, 8, 11],[8, 0, 12],[11, 12, 8]]) np.array([[0, 12, 8],[12, 8, 11],[8, 11, 12]]). Это должно вернуть True, но ваш метод возвращает False в моем тестировании. - person C. Yduqoli; 30.10.2018
comment
Также возникло исключение для [[12 8] [ 8 4]] [[ 6 5] [ 5 14]]: new_order = sidx[df.groupby('A').cumcount().values+searchsorted_idx] IndexError: index 2 is out of bounds for axis 1 with size 2 - person C. Yduqoli; 30.10.2018

Я предполагаю, что у вас есть список перестановок строк/столбцов a, который дает b, например. что-то вроде этого

p = np.array([5, 7, 1, 3, 2, 4, 6]) - 1

Затем вы можете просто сделать следующее на a

a_p = a[p]
a_p = a_p[:, p]

и проверьте, равны ли b и переставленный a_p:

(a_p == b).all()

Изменить: поскольку у вас нет списка, подобного приведенному выше p, вы можете (по крайней мере, для небольших массивов a и b) сгенерировать перестановки индексов и проверить каждый из них:

from itertools import permutations

def a_p(a, b, p):
    p = np.array(p)
    a_p = a[p]
    a_p = a_p[:, p]
    return a_p

for p in permutations(range(a.shape[0])):
    if (a_p(a, b, p) == b).all():
        print('True')
        break
else:
    print('False')

Обратите внимание, что этот метод грубой силы работает и для несимметричных матриц. Но поскольку количество перестановок для больших массивов a и b огромно, этот метод может быть очень медленным. Так что ваше решение с вычислением собственных значений намного лучше.

Вот ориентир:

def Yduqoli(a, b):
    ''' I suppose your solution is similar'''
    if (np.array(np.unique(a, return_counts=True)) == np.array(np.unique(b, return_counts=True))).all():
        a_eigs = np.sort(np.linalg.eigvals(a))
        b_eigs = np.sort(np.linalg.eigvals(b))
        return np.allclose(a_eigs, b_eigs)
    else:
        return False

def AndyK(a, b):
    for p in permutations(range(a.shape[0])):
        if (a_p(a, b, p) == b).all():
            return True
    return False  

%timeit AndyK(a,b)
103 ms ± 4.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit Yduqoli(a,b)
408 µs ± 65.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

где я использовал симметричные матрицы a и b, предоставленные ОП.

Обновление: как упомянул Пол Панзер, простая проверка собственных значений в некоторых случаях может дать неправильный результат, например. a = np.array([[4, 0], [0, 0]]), b = np.array([[2, 2], [2, 2]]) имеют одинаковые собственные значения, но не могут быть вставлены одно в другое. Итак, сначала нам нужно проверить, имеют ли массивы a и b одинаковые элементы (независимо от их положения).

person Andreas K.    schedule 29.10.2018
comment
Извините, у меня нет этого списка. - person C. Yduqoli; 29.10.2018
comment
Вы имеете в виду a = np.array([[4, 0], [0, 0]])? Он симметричен, потому что a = a.T. Но это был произвольный пример. - person Andreas K.; 30.10.2018

Вы всегда можете отсортировать матрицу по норме строки и посмотреть, отличаются ли они. Если две строки имеют одинаковую норму, вам нужно будет проверить перестановки строк, которые имеют одинаковую норму. Но это сводит проблему только к строкам с одинаковой нормой. Во многих случаях вы можете сначала отсортировать по 2-норме, затем по 1-норме и, наконец, перебрать оставшиеся перестановки.

import numpy as np

def get_row_norm(a):
    """
    Sort by 2-norm
    """
    row_norms = np.sum(a**2, axis=1)
    return row_norms

def sort(a):
    """
    Return the matrix a sorted by 2-norm
    """
    n = a.shape[0]
    # Get the norms
    row_norms = get_row_norm(a)
    # Get the order
    order = np.argsort(row_norms)[::-1]

    sorted_a = a.copy()

    for m in range(n):
        i = order[m]
        for k in range(m+1): 
            j = order[k]
            sorted_a[m, k] = a[i, j]
            sorted_a[k, m] = a[i, j]

    return sorted_a


a = np.array([
    #1 #2 #3 #4 #5 #6 #7
    [0, 1, 1, 0, 0, 0, 1], #1
    [1, 0, 1, 2, 1, 1, 2], #2
    [1, 1, 0, 0, 0, 0, 1], #3
    [0, 2, 0, 0, 4, 0, 4], #4
    [0, 1, 0, 4, 0, 1, 2], #5
    [0, 1, 0, 0, 1, 0, 0], #6
    [1, 2, 1, 4, 2, 0, 0]  #7
])  
b = np.array([
    #5 #7 #1,3#3,1#2 #4 #6 
    [0, 2, 0, 0, 1, 4, 1], #5
    [2, 0, 1, 1, 2, 4, 0], #7
    [0, 1, 0, 1, 1, 0, 0], #1,3 could be either
    [0, 1, 1, 0, 1, 0, 0], #1,3 could be either
    [1, 2, 1, 1, 0, 2, 1], #2
    [4, 4, 0, 0, 2, 0, 0], #4
    [1, 0, 0, 0, 1, 0, 0]  #6
])

# Sort a and b
A = sort(a)
B = sort(b)
# Print the norms
print(get_row_norm(a)) # [ 3. 12.  3. 36. 22.  2. 26.]
print(get_row_norm(A)) # [36. 26. 22. 12.  3.  3.  2.]
print(get_row_norm(B)) # [36. 26. 22. 12.  3.  3.  2.]
# Assert that they are equal
print( (A == B).all())

Обратите внимание, что если они не равны, вам все равно придется проверять перестановку пятой и шестой строки, так как их нормы равны.

person user2653663    schedule 29.10.2018
comment
Вот контрпример: a = np.array([[2, 2], [0, 1]]), b = np.array([[1, 0], [2, 2]]), где b равно a с переставленными строками и столбцами. - person Andreas K.; 29.10.2018
comment
@AndyK Но это совсем другая матрица? - person user2653663; 29.10.2018
comment
Я имел в виду, что b это a с переставленными строками 0 ‹-> 1 и столбцами 0 ‹-> 1. - person Andreas K.; 29.10.2018
comment
Но эти матрицы не симметричны, и это то, о чем просил оператор. - person user2653663; 29.10.2018
comment
Действительно вы правы. Я забыл, что они должны быть симметричными. - person Andreas K.; 29.10.2018
comment
А как насчет [[0 1] [1 1]] [[1 1] [1 0]]? После сортировки не сравниваются равные, но у него нет повторяющихся норм строк. - person C. Yduqoli; 30.10.2018
comment
@C.Yduqoli Ты прав. Была ошибка в цикле, выполнявшем сортировку. for k in range(m): должно быть for k in range(m+1). Я исправил это в коде выше. - person user2653663; 30.10.2018