Gurobi學習筆記—矩陣變量及八皇后問題案例
本節將介紹Gurobi中的矩陣變量MVar
,並且以Gurobi案例目錄下的八皇后案例進行解讀
矩陣變量MVar
與tupledict
有所區別。
矩陣變量Mvar
是NumPy ndarray形式的變量,只能使用下標索引,通過Numpy的矩陣與MVar相乘得到線性/多項式矩陣表達式MLinExpr
或MQuadExpr
。
矩陣變量的創建
-
Model.addMVar() 常用
addMVar ( shape, lb=0.0, ub=GRB.INFINITY, obj=0.0, vtype=GRB.CONTINUOUS, name="" ) shape:矩陣向量的維度 lb, ub:分別為變量的上下限(可選) obj:變量在目標函數表達式中的系數(可選) vtype:變量類型(可選) x = model.addMVar(10) # 包含10個變量的一維矩陣變量 y = model.addMVar((3,4), vtype GRB.BINARY) # 3x4的0-1變量矩陣
矩陣變量的運算
Gurobi中的矩陣變量可以當做Numpy中的矩陣,對其進行運算以創建表達式,需要注意的是維數一定要兼容。例如表示如下式子:
x + 2 y + 3 z <= 4
x + y >= 1
x, y, z binary
可寫作
# 一維矩陣變量
x = m.addMVar(shape=3, vtype=GRB.BINARY, name="x")
# 左端項系數矩陣
A = np.array([[1,2,3],[1,1,0]])
# 右端項
rhs = np.array([4.0, -1.0])
m.addConstr(A @ x <= rhs, name ="c")
矩陣變量支持切片操作,使用[]
,:
選取矩陣中特定的元素,這一點與tupledict
對象(即addVars()
)的select()
不同,后者采用*表示通配符運算
矩陣變量支持求和操作,但與tupledict
變量不同。矩陣變量采用切片選取元素,后直接調用sum()
函數
y = model.addMVar((3,4), vtype GRB.BINARY) # 3x4的0-1變量矩陣
y[:,1]#選取第一列元素
與tuplelist對象的對比
tupledict
變量由model.addVars()
或者multidict()
函數創建,通過創建時使用的indices進行訪問,同時具有select()
, sum()
, prod()
的功能篩選元素並快速構建表達式。
model.addVars(*indices)
方式創建的對象,本質上是多種集合的笛卡爾乘積的結果
x = m.addVars(2,3, vtype=GRB.BINARY)
# 生成的元素是
x(0,0) x(0,1) x(0,2)
x(1,0) x(1,1) x(1,2)
# 三個組的笛卡爾積
y = m.addVars((1,2),(1,3),(2,3))
x(1,1,2) x(1,1,3) x(1,3,2) x(1,3,3)
x(2,1,2) x(2,1,3) x(2,3,2) x(2,3,3)
tupledict
對象支持采用字符串類型創建索引(字符串作為key),並且索引可使用通配符(select方法)
注意:方括號索引時不能使用*
MVar僅能通過數字按行列索引
commodities = ['Pencils', 'Pens']
nodes = ['Detroit', 'Denver']
flow = m.addVars(commodities, nodes, obj=cost, name="flow")
flow.select('*','*')
flow[*,*] # 錯誤的引用方式
flow['pencil','Detroit']
flow['Pencils','Denver']
八皇后案例
該案例存在於Gurobi目錄下案例文件夾中根目錄\examples\python\matrix2.py
八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。(摘自百度百科)
假設決策變量x(i,j)
為0-1變量,變量為1代表在第i行第j列放置皇后,需滿足下列條件:
(1) 每行只放置一個皇后
(2)每列只放置一個皇后
(3)每條正對角線只放一個皇后
(4)每個斜對角線只放一個皇后
約束3,4由生成器生成,在代碼中可能一下子看不懂,本人以n=4為例,推了一下正對角線的計算,斜對角線同理
#!/usr/bin/env python3.7
# Copyright 2019, Gurobi Optimization, LLC
# This example uses the Python matrix API to formulate the n-queens
# problem; it maximizes the number queens placed on an n x n
# chessboard without threatening each other.
#
# This example demonstrates NumPy slicing.
import numpy as np
import scipy.sparse as sp
import gurobipy as gp
from gurobipy import GRB
# Size of the n x n chess board
n = 8
try:
# Create a new model
m = gp.Model("matrix2")
# Create a 2-D array of binary variables
# x[i,j]=1 means that a queen is placed at square (i,j)
x = m.addMVar((n, n), vtype=GRB.BINARY, name="x")
# Set objective - maximize number of queens
m.setObjective(x.sum(), GRB.MAXIMIZE)
# Add row and column constraints
# 為每一行、列添加約束
for i in range(n):
# At most one queen per row
# 每一行最多放置一個皇后
m.addConstr(x[i, :].sum() <= 1, name="row"+str(i))
# At most one queen per column
# 每一列最多放置一個皇后
m.addConstr(x[:, i].sum() <= 1, name="col"+str(i))
# Add diagonal constraints
# 添加對角線約束
for i in range(1, 2*n):
# At most one queen per diagonal
# 每個正對角線最多防止一個皇后
# 生成器生成對矩陣的切片索引
diagn = (range(max(0, i-n), min(n, i)), range(min(n, i)-1, max(0, i-n)-1, -1))
m.addConstr(x[diagn].sum() <= 1, name="diag"+str(i))
# At most one queen per anti-diagonal
# 每個斜對角線最多防止一個皇后
# 生成器生成對矩陣的切片索引
adiagn = (range(max(0, i-n), min(n, i)), range(max(0, n-i), min(n, 2*n-i)))
m.addConstr(x[adiagn].sum() <= 1, name="adiag"+str(i))
# Optimize model
# 開始優化模型
m.optimize()
print(x.X)
print('Queens placed: %g' % m.objVal)
except gp.GurobiError as e:
print('Error code ' + str(e.errno) + ": " + str(e))
except AttributeError:
print('Encountered an attribute error')