提问人:Ryan 提问时间:11/5/2023 最后编辑:Ryan 更新时间:11/5/2023 访问量:63
根据矩阵对矩阵中的元素进行排序
Order elements in a matrix based on another matrix
问:
我想根据描述元素属性的二进制矩阵 () 中的值,将任意数量的元素排序到任意形状的矩阵 () 中。 定义了 中可以彼此相邻的 。在这种情况下,“相邻”包括对角线。一个具体但玩具的例子:matrix_a
element_map
matrix_b
elements
matrix_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 表示属性。elements
matrix_a
matrix_b
element
matrix_a
element
x
matrix_a
elements
x
还假设我们有一个函数,可以接受矩阵和坐标并返回所有相邻的值:
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!
也许这将是线性/整数编程的工作,但我不确定如何制定邻接约束
答:
这可以通过ILP(整数线性规划)来完成。粗略的切割如下所示。您似乎没有声明权重来考虑一个结果优于另一个结果,因此您也可能很幸运地使用 CP(约束规划)方法。下面的 ILP 使用成功元素填充的数量作为目标。可能有很多解决方案(或没有解决方案),它只会抓住第一个发现的解决方案。
值得注意的是,您提供的示例没有针对 3x3 目标矩阵的解决方案。我使用下面的部分从单位矩阵(翻转 1/0)中制作元素,通过检查,这是最适合拟合元素列表的方法。
我稍微调整了你的函数,因为你有一个混合模式,你接受(x,y)并返回一个编号的位置。我刚刚做到了这一切(x,y)adj()
如果有解,对于中等大小的 ,这往往会很快找到它,尤其是在解集很大的情况下。matrix_a
我在这里使用了求解器,这是不错的免费软件,但还有其他 MIP 求解器可以正常工作(glpk、highs......cbc
这里仍然有一些“骨头上的肉”来优化这一点,主要是通过域将元组修剪为基于值的合法赋值,这意味着任何具有非零属性的元素都是不合法的,应该从 的域中删除。然后,您可以考虑预先计算任何给定元素的可能邻居,以稍微减小约束的大小。[e, p, a]
a
place[e, p, a]
e
a
place
adjacency
法典:
"""
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!
评论