Source code for mapof.core.matchings
import gurobipy as gp
import numpy as np
from gurobipy import GRB
from scipy.optimize import linear_sum_assignment
[docs]
def solve_matching_vectors(cost_table: list[list]) -> (float, list):
"""
Computes linear sum assignment.
Parameters
----------
cost_table : list[list]
Cost table.
Returns
-------
(float, list)
Objective value, Optimal matching
"""
cost_table = np.array(cost_table)
row_ind, col_ind = linear_sum_assignment(cost_table)
return cost_table[row_ind, col_ind].sum(), list(col_ind)
[docs]
def solve_matching_matrices(
matrix_1: list[list],
matrix_2: list[list],
length: int,
inner_distance: callable
) -> float:
"""
Computes the minimal distance between two matrices.
We assume that both matrices are square matrices of the same size with zeros on the diagonal.
We allow reordering of the rows and columns of the second matrix, however, whenever we reorder
a row we have to reorder the corresponding column as well, and vice versa.
We use the Gurobi optimization library to solve the assignment problem.
Parameters
----------
matrix_1 : list[list]
First square matrix.
matrix_2 : list[list]
Second square matrix.
length : int
Length of the matrix.
inner_distance : callable
The inner distance (like L1 or L2).
Returns
-------
float
Objective value.
"""
m = gp.Model()
m.ModelSense = GRB.MINIMIZE
# OBJECTIVE FUNCTION
variables = {}
for k in range(length):
for l in range(length):
for i in range(length):
if i == k:
continue
for j in range(length):
if j == l:
continue
weight = inner_distance(np.array([matrix_1[k][i]]), np.array([matrix_2[l][j]]))
name = f'Pk{k}l{l}i{i}j{j}'
variables[name] = m.addVar(vtype=GRB.BINARY, name=name, obj=weight)
# ADD MISSING VARIABLES
for i in range(length):
for j in range(length):
name = f'Mi{i}j{j}'
variables[name] = m.addVar(vtype=GRB.BINARY, name=name)
m.update()
# CONSTRAINTS
for k in range(length):
for l in range(length):
for i in range(length):
if i == k:
continue
for j in range(length):
if j == l:
continue
m.addConstr(variables[f'Pk{k}l{l}i{i}j{j}'] - variables[f'Mi{i}j{j}'] <= 0)
m.addConstr(variables[f'Pk{k}l{l}i{i}j{j}'] - variables[f'Mi{k}j{l}'] <= 0)
for i in range(length):
m.addConstr(gp.quicksum(variables[f'Mi{i}j{j}'] for j in range(length)) == 1)
for j in range(length):
m.addConstr(gp.quicksum(variables[f'Mi{i}j{j}'] for i in range(length)) == 1)
for k in range(length):
for i in range(length):
if k == i:
continue
m.addConstr(gp.quicksum(variables[f'Pk{k}l{l}i{i}j{j}']
for l in range(length)
for j in range(length) if l != j) == 1)
for l in range(length):
for j in range(length):
if l == j:
continue
m.addConstr(gp.quicksum(variables[f'Pk{k}l{l}i{i}j{j}']
for k in range(length)
for i in range(length) if k != i) == 1)
# SOLVE THE ILP
m.setParam('OutputFlag', 0)
m.optimize()
for var_name, var in variables.items():
print(f"{var_name}: {var}")
for constr in m.getConstrs():
print(constr)
if m.status == GRB.OPTIMAL:
objective_value = m.objVal
return objective_value
else:
print("Exception raised while solving")