松約束和緊約束是針對不等式約束而言的,如果在一個解中侣签,不等式約束左端項(xiàng)的值和右端項(xiàng)的值相等塘装,那么它就是一個緊約束;反之影所,則是一個松約束蹦肴。
在運(yùn)籌學(xué)的問題建模與求解中,約束的放松收緊是調(diào)節(jié)可行域的重要手段猴娩。
在CPLEX求解問題后阴幌,我們有時候想要知道在得到的解中,哪些約束是緊約束卷中,哪些約束是松約束矛双。
下面我們就用一個例子來看一下如何在CPLEX中查看對某個解,某個約束是否是緊約束蟆豫。
問題描述
這里用清華大學(xué)出版社的運(yùn)籌學(xué)第四版第106頁的運(yùn)輸問題舉個例子:
某公司有三個加工廠议忽,生產(chǎn)同一種產(chǎn)品。它的主要銷售地有4個十减,從每個工廠出發(fā)栈幸,到銷售地的單位運(yùn)價如下表所示愤估,單位為元/噸:
銷售地B1 | 銷售地B2 | 銷售地B3 | 銷售地B4 | |
---|---|---|---|---|
工廠A1 | 16 | 13 | 22 | 17 |
工廠A2 | 14 | 13 | 19 | 15 |
工廠A3 | 19 | 20 | 23 | - |
而三個工廠的每日產(chǎn)量分別為50,60速址,50噸玩焰。每個銷售地的每日最小需求量為30,70芍锚,0震捣,10噸;每日最大需求量為50闹炉,70蒿赢,30,不限渣触。
求出總運(yùn)費(fèi)最節(jié)省的產(chǎn)品運(yùn)輸方案羡棵。
模型
# 導(dǎo)入包
from docplex.mp.model import Model
import numpy as np
# 創(chuàng)建數(shù)據(jù)
n_factory: int = 3
n_market: int = 4
I: list = list(range(n_factory)) # 工廠下標(biāo)的集合
J: list = list(range(n_market)) # 銷售地下標(biāo)的集合
bigM: int = 1000 # 大M
# 設(shè)置單位運(yùn)費(fèi)
cost_mat = [[16, 13, 22, 17],
[14, 13, 19, 15],
[19, 20, 23, bigM]]
min_demand: list = [30, 70, 0, 10] # 最小需求
max_demand: list = [50, 70, 30, bigM] # 最大需求
supply: list = [50, 60, 50] # 工廠的每日產(chǎn)量
# 建立模型
mdl = Model("Transportation Problem", cts_by_name = True)
x = mdl.continuous_var_dict([(i, j) for i in I for j in J], name = "volume")
# 設(shè)定目標(biāo)函數(shù) - 總的運(yùn)送成本最低
mdl.minimize(mdl.sum(cost_mat[i][j] * x[(i, j)] for i in I for j in J))
# 添加約束
# 1. 所有工廠產(chǎn)量都要運(yùn)送出去
mdl.add_constraints((mdl.sum(x[(i,j)] for j in J) == supply[i] for i in I), names="SupplyAmountConstraint")
# 2. 每個銷售地得到的總量介于最小需求和最大需求之間
mdl.add_constraints((mdl.sum(x[(i,j)] for i in I) <= max_demand[j] for j in J), names="MaxDemandConstraint")
mdl.add_constraints((mdl.sum(x[(i,j)] for i in I) >= min_demand[j] for j in J), names="MinDemandConstraint")
# 求解
sol = mdl.solve()
# 輸出結(jié)果
print(sol.as_df())
可以得到這個問題的解:
name value
0 volume_0_1 50.0
1 volume_1_1 20.0
2 volume_1_3 40.0
3 volume_2_0 50.0
也就是說,工廠A1運(yùn)送到銷售地B2的量為50噸嗅钻,工廠A2運(yùn)送到銷售地B2的量為20噸皂冰,運(yùn)送到銷售地B3的量為40噸,工廠A3運(yùn)送到銷售地B1的量為50噸時养篓,總運(yùn)輸費(fèi)用最低秃流。
查看緊約束
在CPLEX中并沒有原生函數(shù)能幫我們查看一個約束是緊約束還是松約束,這里我們可以間接實(shí)現(xiàn)松緊約束的判斷:
對每個不等式約束的左端項(xiàng)和右端項(xiàng)進(jìn)行求值柳弄,然后判定是否相等舶胀。
如果相等,則為緊約束碧注,否則為松約束嚣伐。
from docplex.mp.constants import ComparisonType
# 查看哪些約束是緊約束
tol: float = 1e-5
for ct in mdl.iter_constraints():
# 跳過等式約束
if ct.sense == ComparisonType.EQ:
continue
print("-----------------")
print("約束名: {}".format(ct.name))
lhs: float = sol.get_value(ct.get_left_expr())
rhs: float = sol.get_value(ct.get_right_expr())
print("左端項(xiàng): {}".format(lhs))
print("右端項(xiàng): {}".format(rhs))
if abs(lhs - rhs) < tol:
print("緊約束")
else:
print("松約束")
得到如下結(jié)果,可以看到我們只保留了不等式約束萍丐,其中
-----------------
約束名: MaxDemandConstraint1
左端項(xiàng): 50.0
右端項(xiàng): 50
緊約束
-----------------
約束名: MaxDemandConstraint2
左端項(xiàng): 70.0
右端項(xiàng): 70
緊約束
-----------------
約束名: MaxDemandConstraint3
左端項(xiàng): 0
右端項(xiàng): 30
松約束
-----------------
約束名: MaxDemandConstraint4
左端項(xiàng): 40.0
右端項(xiàng): 1000
松約束
-----------------
約束名: MinDemandConstraint1
左端項(xiàng): 50.0
右端項(xiàng): 30
松約束
-----------------
約束名: MinDemandConstraint2
左端項(xiàng): 70.0
右端項(xiàng): 70
緊約束
-----------------
約束名: MinDemandConstraint3
左端項(xiàng): 0
右端項(xiàng): 0
緊約束
-----------------
約束名: MinDemandConstraint4
左端項(xiàng): 40.0
右端項(xiàng): 10
松約束