Генерация всех возможных двоичных матриц, чтобы каждая строка и столбец складывались в AT MOST 1 для каждой матрицы - PullRequest
1 голос
/ 15 марта 2020

Я пытаюсь сгенерировать все возможные квадратные двоичные матрицы для данного n, так что: для каждой двоичной матрицы:

1) the rows sum up to at most 1
2) the columns sum up to at most 1

Пример: для n = 2 действительные матрицы:

[0 0]
[0 0]

[0 0]
[0 1]

[0 0]
[1 0]

[0 1]
[0 0]

[0 1]
[1 0]

[1 0]
[0 0]

[1 0]
[0 1]

Мне нужно сгенерировать все эти матрицы

В python у меня есть следующий грубый способ сделать это прямо сейчас для n = k

    allwords = list(it.product(*([(0, 1)] * (n**2)))) # Generate all possible binary matrices
    allarrays = map(np.asarray, allwords)  # Convert to array # Convert them toarray
    allmatrices = [a.reshape(n, self.n) for a in allarrays]  # Matrixify # Make a matrix
# The following checks if the matrix has row sum at most 1 and column sum at most 1
    validActions = [x for x in allmatrices if contains(x)]  # Final list has only vlaid matrices

Параметр содержимого определен как

def contains(x): # Checks if row and column sums are at most 1 for each entry
        colSums = np.sum(x, axis=0)
        rowSums = np.sum(x, axis=1)
        return (np.all(colSums <= 1) and np.all(rowSums <= 1)) and np.all(x >= 0)

Это в значительной степени разрывается при n = 5 или выше, поэтому мне нужен более умный способ сделать это.

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

Ответы [ 3 ]

2 голосов
/ 16 марта 2020

Вот еще один подход, который генерирует все такие матрицы, используя пространство O(n^2), основанное на itertools.permutations:

from itertools import combinations, permutations

def all_bin_mats(n):
  mat_template = np.zeros((n, n), dtype=np.int8)
  # all zeros case
  yield mat_template.copy()
  for k in range(1, n+1):
    for row_subset in combinations(range(n), k):
      row_idx = np.array(list(row_subset))
      for col_subset in combinations(range(n), k):
        col_idx = np.array(list(col_subset))
        for perm in permutations(range(k)):
          mat = mat_template.copy()
          mat[row_idx, col_idx[list(perm)]] = 1
          yield mat

assert sum(1 for _ in all_bin_mats(2)) == 7
assert sum(1 for _ in all_bin_mats(3)) == 34
assert sum(1 for _ in all_bin_mats(8)) == 1441729
1 голос
/ 16 марта 2020

Эта проблема на самом деле является только проблемой грачей с доской n x n и ладьями m < n. Вот способ решения проблемы, который, хотя и не является оптимальным, но я надеюсь, вы найдете его интересным.

Сначала мы определим альтернативный способ описания двоичной матрицы размером n x n. Рассмотрим разбиение n чисел на пути и циклы. У строки есть единица, если она имеет входящее ребро. Источник входящего фронта указывает на столбец, в котором он находится. Примеры:

0->2, 1  
Row 2 has a one in column zero since it has an incoming edge from 0
0 0 0
0 0 0
1 0 0
---
0->2->0, 1->1
Row 2 has a one in column 0 since it has an incoming edge from 0
Row 0 has a one in column 2 since it has an incoming edge from 2
Row 1 has a one in column 1 since it has an incoming edge from 1
0 0 1
0 1 0
1 0 0
---
  1. Таким образом, мы сначала разбьем n числа на npart разбиения. (Нам нужно будет рассмотреть все разделы)
    • пример: мы разбиваем 0, 1, 2, 3 на 2 раздела: [0, 1, 2], [3].
  2. Мы разрешим каждому разделу любой вид цикл или путь (обратите внимание, что пути не являются циклическими c). (Нам нужно будет рассмотреть все комбинации циклов / путей)
    • пример: мы позволяем [0, 1, 2] формировать цикл и [3] формировать путь.
  3. Мы переставлять каждый раздел в зависимости от того, цикл это или путь. (мы выполним эту часть рекурсивно, чтобы рассмотреть все схемы)
    • Пример: [0, 1, 2] может образовывать только циклы 0-1-2-0 и 0-2-1-0. Обратите внимание, что 1-2-0-1 совпадает с 0-1-2-0.
    • Пример: если мы выбрали [0, 1, 2] для формирования элементов пути, то все перестановки длины 3 действительны: т.е. 0-1-2, 0-2-1, 1-0-2, 1-2-0, 2-0-1, 2-1-0.
import numpy as np
import itertools
import more_itertools

def printMatrix(part, iscyc):
    ones = -1*np.ones(n, dtype=int) # ones[2] = 4 means row 2 col 4 is 1, ones[2] = -1 means row 2 is all 0
    for i, p in enumerate(part):
        for j in range(len(p)-1):
            ones[p[j+1]] = p[j]
        if iscyc[i]:
            ones[p[0]] = p[-1]

    for r in range(n):
        for c in range(n):
            print(f"{1 if c == ones[r] else 0} ", end="")
        print("")
    print("")


def permute_part(head, tail, iscyc):
    # head contains permuted partitions
    # tail contains all partitions that haven't been permuted yet
    global count

    if not tail:
        # empty tail means that all partitions have been permuted, and we have arrived at a valid solution
        count += 1
        printMatrix(head, iscyc)
        return

    p = tail[0]
    perms = list(itertools.permutations(p))
    while perms:
        perm = perms.pop()
        if iscyc[len(head)]:
            # only keep distinct cycles in permutations
            cshifts = list(more_itertools.circular_shifts(perm))
            perms = [x for x in perms if x not in cshifts]

        permute_part(head+[list(perm)], tail[1:], iscyc)


def all_part(part, iscyc):
    permute_part([], part, iscyc)

n = 2
count = 0
for part in more_itertools.set_partitions(range(n)):
    npart = len(part)
    for iscyc in itertools.product(range(2), repeat=npart):
        # i-th element in iscyc tells us whether partition i is a cycle
        # now we have a path/cycle partition
        all_part(part, iscyc)
print(f"total: {count}")

Выход для n = 2

0 1 
0 0 

0 0 
1 0 

0 1 
1 0 

0 0 
0 0 

0 0 
0 1 

1 0 
0 0 

1 0 
0 1 

total: 7

n=4 дает 32, как и решение, заданное hilberts_drinking_problem . n=8 дает 1441729, как и решение, заданное hilberts_drinking_problem .


Исходный ответ (неверный, поскольку рассматриваются только случаи, когда все строки равны 1)

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

import numpy as np

def printArr(a):
    print('------')
    for i in range(n): 
        for j in range(n):
            print(a[i, j],end=" ") 
        print()

def heapPermutation(a, size, n): 

    # if size becomes 1 then prints the obtained 
    # permutation 
    if (size == 1): 
        printArr(I[a]) # permute the rows of I
        return

    for i in range(size): 
        heapPermutation(a,size-1,n); 

        # if size is odd, swap first and last 
        # element 
        # else If size is even, swap ith and last element 
        if size&1: 
            a[0], a[size-1] = a[size-1],a[0] 
        else: 
            a[i], a[size-1] = a[size-1],a[i] 

n = 3
I = np.identity(n, dtype=int) # use dtype=bool if that is sufficient
a = np.arange(n)
heapPermutation(a, n, n)

Результат:

------
1 0 0
0 1 0
0 0 1
------
0 1 0
1 0 0
0 0 1
------
0 0 1
1 0 0
0 1 0
------
1 0 0
0 0 1
0 1 0
------
0 1 0
0 0 1
1 0 0
------
0 0 1
0 1 0
1 0 0

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

1 голос
/ 15 марта 2020

Вместо этого вы можете рассмотреть рекурсивное решение. Рассмотрим генерацию всех таких nxn матриц. Сначала сгенерируйте верхнюю строку: в одной позиции либо 1, либо все нули. Для первого случая сгенерируйте все возможные (n-1) x (n-1) матрицы, а затем для каждой позиции столбца и каждой меньшей матрицы сгенерируйте матрицу nxn, вставив новую строку сверху. Например, если у вас есть подматрица

0 1
1 0

, вы можете сгенерировать матрицы 3x3

1 0 0   0 1 0   0 0 1
0 0 1   0 0 1   0 1 0
0 1 0   1 0 0   1 0 0

, вставив строку сверху и новый дополнительный столбец.

Затем, чтобы обработать строку из всех нулей, просто сгенерируйте все возможные (n-1) матрицы xn и добавьте нулевую строку вверху.

Математически это сгенерирует ровно все матрицы, которые вы хотите.

...