根据矩阵对矩阵中的元素进行排序

Order elements in a matrix based on another matrix

提问人:Ryan 提问时间:11/5/2023 最后编辑:Ryan 更新时间:11/5/2023 访问量:63

问:

我想根据描述元素属性的二进制矩阵 () 中的值,将任意数量的元素排序到任意形状的矩阵 () 中。 定义了 中可以彼此相邻的 。在这种情况下,“相邻”包括对角线。一个具体但玩具的例子:matrix_aelement_mapmatrix_belementsmatrix_a

import numpy as np

#This is a just a mask of the final matrix, to show the shape.
#The center position (5) is adjacent to all other positions whereas
#position (1) is adjacent to 4, 2 and 5, etc. 
matrix_a = np.array([[1, 4, 7],
                     [2, 5, 8],
                     [3, 6, 9]])

elements = range(0, 10)

#each 'row' corresponds to an element
#each 'column' corresponds to an attribute of the various elements
#here, each element has 5 attributes, which are always 0 or 1
element_map = np.array([[0, 0, 1, 0, 1], #element 1
                        [1, 1, 1, 0, 1], #element 2
                        [1, 0, 0, 1, 0], #element 3
                        [1, 0, 1, 0, 0], #etc.
                        [1, 1, 1, 0, 0],
                        [1, 1, 0, 1, 0],
                        [1, 0, 1, 0, 1],
                        [1, 1, 0, 0, 0],
                        [1, 1, 0, 0, 0]]) #element 9

期望的输出是将这 9 个放在 中,每个只出现一次,具体取决于所需的邻接规则。规则是,对于任何放置在 中,属性(可以是任何属性)的值必须为零,而所有相邻(相邻 )的值必须为 1 表示属性。elementsmatrix_amatrix_belementmatrix_aelementxmatrix_aelementsx

还假设我们有一个函数,可以接受矩阵和坐标并返回所有相邻的值:

def adj(m, x, y):
    ''' find adjacent values to coordinates x,y in a matrix, m'''
    if x == 0:
        if y == 0:
            r = m[0:2,0:2] # x==0, y==0
        else:
            r = m[0:2,y-1:y+2]  # x==0, y!=0
    elif y == 0: # x != 0, y == 0
        r = m[x-1:x+2,0:2]
    else: #any other position
        r = m[(x-1):(x+2),(y-1):(y+2)]
        
    return [i for i in r.flatten().tolist() if i != m[x,y]]

#example:
q = np.arange(1,97).reshape(12,8).T

array([[ 1,  9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89],
       [ 2, 10, 18, 26, 34, 42, 50, 58, 66, 74, 82, 90],
       [ 3, 11, 19, 27, 35, 43, 51, 59, 67, 75, 83, 91],
       [ 4, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92],
       [ 5, 13, 21, 29, 37, 45, 53, 61, 69, 77, 85, 93],
       [ 6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94],
       [ 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95],
       [ 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96]])

adj(q, 5, 5)
[37, 45, 53, 38, 54, 39, 47, 55]

顺便说一句,8x12 矩阵和 96 个元素的可能搜索空间是 10**149 = 96!

也许这将是线性/整数编程的工作,但我不确定如何制定邻接约束

Python 优化 稀疏矩阵 整数规划

评论


答:

1赞 AirSquid 11/5/2023 #1

这可以通过ILP(整数线性规划)来完成。粗略的切割如下所示。您似乎没有声明权重来考虑一个结果优于另一个结果,因此您也可能很幸运地使用 CP(约束规划)方法。下面的 ILP 使用成功元素填充的数量作为目标。可能有很多解决方案(或没有解决方案),它只会抓住第一个发现的解决方案。

值得注意的是,您提供的示例没有针对 3x3 目标矩阵的解决方案。我使用下面的部分从单位矩阵(翻转 1/0)中制作元素,通过检查,这是最适合拟合元素列表的方法。

我稍微调整了你的函数,因为你有一个混合模式,你接受(x,y)并返回一个编号的位置。我刚刚做到了这一切(x,y)adj()

如果有解,对于中等大小的 ,这往往会很快找到它,尤其是在解集很大的情况下。matrix_a

我在这里使用了求解器,这是不错的免费软件,但还有其他 MIP 求解器可以正常工作(glpk、highs......cbc

这里仍然有一些“骨头上的肉”来优化这一点,主要是通过域将元组修剪为基于值的合法赋值,这意味着任何具有非零属性的元素都是不合法的,应该从 的域中删除。然后,您可以考虑预先计算任何给定元素的可能邻居,以稍微减小约束的大小。[e, p, a]aplace[e, p, a]eaplaceadjacency

法典:

"""
fit elements into matrix based on adjacency rules
"""

import numpy as np
import pyomo.environ as pyo


class Element:
    """a convenience to hold the rows of attribute values"""
    def __init__(self, row):
        self.attributes = tuple(row)

    def attribute(self, idx):
        return self.attributes[idx]

    def __repr__(self):
        return str(self.attributes)


class Position:
    """a convenience for (x, y) positions that must have equality & hash defined for consistency"""
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'({self.x}, {self.y})'

    def __hash__(self):
        return hash((self.x, self.y))

    def __eq__(self, other):
        if isinstance(other, Position):
            return (self.x, self.y) == (other.x, other.y)
        return False

# each 'row' corresponds to an element
# each 'column' corresponds to an attribute of the various elements
# here, each element has 5 attributes, which are always 0 or 1
element_map = np.array([[0, 0, 1, 0, 1],  # element 1
                        [1, 1, 1, 0, 1],  # element 2
                        [1, 0, 0, 1, 0],  # element 3
                        [1, 0, 1, 0, 0],  # etc.
                        [1, 1, 1, 0, 0],
                        [1, 1, 0, 1, 0],
                        [1, 0, 1, 0, 1],
                        [1, 1, 0, 0, 0],
                        [1, 1, 0, 0, 0]])  # element 9

# Alternate element map...
d = 5
element_map = np.ones((d, d), dtype=int) - np.eye(d, dtype=int)
print(element_map)

matrix_a_rows = 3
matrix_a_cols = 3
matrix_a = np.zeros((matrix_a_rows, matrix_a_cols))


def adj_xy(mat, p: Position):
    x, y = p.x, p.y
    res = []
    rows = len(mat) - 1
    cols = len(mat[0]) - 1
    for i in range(x - 1, x + 2):
        for j in range(y - 1, y + 2):
            if all((0 <= i <= rows, 0 <= j <= cols, (i, j) != (x, y))):
                res.append(Position(i, j))
    return res


# SET UP ILP
m = pyo.ConcreteModel('matrix_fitter')

# SETS

m.E = pyo.Set(initialize=[Element(row) for row in element_map], doc='elements')
m.P = pyo.Set(initialize=[Position(x, y) for x in range(len(matrix_a)) for y in range(len(matrix_a[0]))],
              doc='positions')
m.A = pyo.Set(initialize=list(range(len(element_map[0]))), doc='attribute')

# VARS
# place element e in position p based on attribute a being 0...
m.place = pyo.Var(m.E, m.P, m.A, domain=pyo.Binary, doc='place')

# OBJ:  Place as many as possible....  ideally --> len(matrix_a)
m.obj = pyo.Objective(expr=pyo.sum_product(m.place), sense=pyo.maximize)


# CONSTRAINTS
@m.Constraint(m.E, m.P, m.A)
def zero_attribute(m, e: Element, p, a):
    # can only place if attribute a is zero
    return m.place[e, p, a] * e.attribute(a) == 0


@m.Constraint(m.P)
def only_one(m, p):
    # can only place at most one element at each location
    return sum(m.place[e, p, a] for e in m.E for a in m.A) <= 1


@m.Constraint(m.E, m.P, m.A)
def adjacency(m, e: Element, p: Position, a):
    # neighbors must "add up" in the selected attribute (a), if placed in neighbor positions
    # we can "add up" the values of the selected attribute (a) for all adjacent positions that were selected
    neighbor_positions = adj_xy(matrix_a, p)
    return (sum(m.place[ee, pp, aa] * ee.attribute(a) for ee in m.E for pp in neighbor_positions for aa in m.A) >=
            len(neighbor_positions) * m.place[e, p, a])


solver = pyo.SolverFactory('cbc')
results = solver.solve(m, tee=True)
print(results)
if results.solver.termination_condition == pyo.TerminationCondition.optimal:
    for idx in m.place.index_set():
        if m.place[idx].value == 1:
            print(idx)
    if pyo.value(m.obj) == matrix_a_rows * matrix_a_cols:
        # all positions were filled
        print('success!')
    else:
        print(f'the max number of elements that can be placed is {pyo.value(m.obj)} / {matrix_a_rows * matrix_a_cols}')

else:
    print('Problem with model...see results')

输出(截断):

((0, 1, 1, 1, 1), (0, 1), 0)
((0, 1, 1, 1, 1), (2, 1), 0)
((1, 1, 0, 1, 1), (1, 0), 2)
((1, 1, 0, 1, 1), (1, 2), 2)
((1, 1, 1, 0, 1), (1, 1), 3)
((1, 1, 1, 1, 0), (0, 0), 4)
((1, 1, 1, 1, 0), (0, 2), 4)
((1, 1, 1, 1, 0), (2, 0), 4)
((1, 1, 1, 1, 0), (2, 2), 4)
success!

评论

0赞 Ryan 11/5/2023
太棒了,谢谢!这是一个美丽的起点