数模算法之线性规划

OIer入门数模竞赛代码手之路

在生产实践中,经常会遇到如何利用现有资源安排生成,以取得最大效益的问题。运筹学的一个重要分支——数学规划应运而生,它是一种通过数学方法来解决实际问题的方法。线性规划是数学规划的一个重要分支,它是一种在给定约束条件下,求解线性目标函数最优值的方法。

本系列偏代码向,入门之前需要了解一些基本的数学知识,如线代、微积分等。本系列(由于一些懂得都懂的原因)以Python为主,也会涉及Matlab

线性规划问题

举个例子

某厂家生产甲乙两种产品,每件利润分别为4000,30004000,3000元。生产甲产品需用A,BA,B两种机器加工,每件分别需要2h,1h2h,1h;生成乙产品需用A,B,CA,B,C三种机器加工,每件每种机器1h1h。机器每天可运行时长为10h,8h,7h10h,8h,7h。问如何安排生产计划,使得利润最大?

对上述问题建立数学模型:设甲乙产品的生产量分别为x1,x2x_1,x_2时,总利润z=4000x1+3000x2z=4000x_1+3000x_2最大,则:

maxz=4000x1+3000x2s.t.{2x1+x210x1+x28x27x1,x20\max z=4000x_1+3000x_2\\ s.t. \begin{cases} 2x_1 + x_2 \leq 10\\ x_1 + x_2 \leq 8\\ x_2 \leq 7\\ x_1 , x_2 \geq 0 \end{cases}

其中,x1,x2x_1,x_2称为决策变量,函数z=4000x1+3000x2z=4000x_1+3000x_2称为目标函数,s.t.标记的不等式组称为约束条件(即subject to)。由于上述目标函数与约束条件均为线性,故称线性规划。

线性规划问题的解

一般线性规划问题的(Matlab/Python)标准形式为:

minz=fTxs.t.{A×xbAeq×x=Beqlbxub\min z = f^T x\\ s.t.\begin{cases} A\times x \leq b\\ Aeq \times x = Beq\\ lb \leq x \leq ub \end{cases}

式中f,x,b,Beq,lb,ub为列向量,A,Aeq为矩阵,其中称f(也可用c表示)为价值向量,A为不等式约束矩阵,b为不等式约束向量(价值向量),Aeq为等式约束矩阵,Beq为等式约束向量,lb为下界向量,ub为上界向量。

满足约束条件的x即称为线性规划的可行解。所有可行解构成的集合称为可行域,记作RR。其中,目标函数z最小的可行解称为最优解

代码实现

利用Python求解线性规划问题需要导入numpy,scipy库。以上述问题为例,其数学模型转化为标准形式为:

minz=(40003000)T×(x1x2)s.t.{(211101)(x1x2)(1087)(x1x2)(00)\min z = -\begin{pmatrix} -4000 \\ -3000 \end{pmatrix}^T \times \begin{pmatrix} x_1 \\ x_2 \end{pmatrix}\\ s.t.\begin{cases} \begin{pmatrix} 2 & 1\\ 1 & 1\\ 0 & 1 \end{pmatrix} \begin{pmatrix} x_1 \\ x_2 \end{pmatrix} \leq \begin{pmatrix} 10 \\ 8 \\ 7 \end{pmatrix}\\ \begin{pmatrix} x_1 \\ x_2 \end{pmatrix} \geq\begin{pmatrix} 0 \\ 0 \end{pmatrix} \end{cases}

将参数定义为numpy矩阵后调用函数求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from scipy import optimize
import numpy as np

f = np.array([-4000,-3000]).transpose()
A = np.array([[2,1],[1,1],[0,1]])
b = np.array([10,8,7])

#求解
res = optimize.linprog(c=f,A_ub=A,b_ub=b,bounds=np.array([[0,None],[0,None]]))
print(res)

'''
result:
message: Optimization terminated successfully. (HiGHS Status 7: Optimal)
success: True
status: 0
fun: -26000.0
x: [ 2.000e+00 6.000e+00]
nit: 2
lower: residual: [ 2.000e+00 6.000e+00]
marginals: [ 0.000e+00 0.000e+00]
upper: residual: [ inf inf]
marginals: [ 0.000e+00 0.000e+00]
eqlin: residual: []
marginals: []
ineqlin: residual: [ 0.000e+00 0.000e+00 1.000e+00]
marginals: [-1.000e+03 -2.000e+03 -0.000e+00]
mip_node_count: 0
mip_dual_bound: 0.0
mip_gap: 0.0
'''

其中linprog的完整参数:

1
2
3
def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None,
bounds=None, method='interior-point', callback=None,
options=None, x0=None)

其中:

参数 意义
c 目标函数的决策变量对应的系数向量(行列向量都可以,下同)
A_ub 不等式约束组成的决策变量系数矩阵
b_ub 由A_ub对应不等式顺序的阈值向量
A_eq 等式约束组成的决策变量系数矩阵
b_eq 由A_ub对应等式顺序的阈值向量
bounds 表示决策变量x连续的定义域的n×2维矩阵,None表示无穷
method 调用的求解方法,2.2节给出详细解释
callback 选择的回调函数
options 求解器选择的字典
x0 初始假设的决策变量向量,若可行linprog会对方法优化

求解线性规划问题的Matlab函数为:

1
2
3
[x,val] = linprog(f,A,b);
[x,val] = linprog(f,A,b,Aeq,Beq);
[x,val] = linprog(f,A,b,Aeq,Beq,lb,ub);

其中x为决策向量,val为此时目标函数值。

可转化为线性规划的问题

有些看起来是非线性的问题,经过转换也可以通过线性规划求解。

目标函数含绝对值

对于目标函数含绝对值的问题,可以通过引入新的变量进行转化。如:

minz=x1+2x2+3x3+4x4s.t.{x1x2x3+x42x1x2+x33x41x1x22x3+3x412x1,x2,x3,x40\min z = |x_1|+2|x_2|+3|x_3|+4|x_4|\\ s.t.\begin{cases} x_1-x_2-x_3+x_4 \leq -2\\ x_1-x_2+x_3-3x_4 \leq -1\\ x_1-x_2-2x_3+3x_4 \leq -\frac{1}{2}\\ x_1,x_2,x_3,x_4 \geq 0 \end{cases}

对于绝对值问题,发现xi,ui,vi0:xi=uivi,xi=ui+vi\forall x_i,\exist u_i,v_i \geq 0:x_i=u_i-v_i,|x_i|=u_i+v_i,只需求出u,vu,v就能求出对应x=uvx=u-v

因此做变量变换并将新变量排序为一维向量y=(uv)=(u1...u4v1...v4)y=\begin{pmatrix}u\\v\end{pmatrix}=\begin{pmatrix}u_1&...&u_4&v_1&...&v_4\end{pmatrix}

则上述模型转化为如下标准型:

mincTys.t.{(AA)×(uv)bu,v0\min c^T y\\ s.t.\begin{cases} \begin{pmatrix} A & -A \end{pmatrix} \times \begin{pmatrix} u \\ v \end{pmatrix}\leq b\\ u,v \geq 0 \end{cases}

其中:

c=(12341234)Tb=(2112)TA=(111111131123)c=\begin{pmatrix} 1&2&3&4&1&2&3&4 \end{pmatrix}^T\\ b=\begin{pmatrix} -2&-1&-\frac{1}{2} \end{pmatrix}^T\\ A = \begin{pmatrix} 1&-1&-1&1\\ 1&-1&1&-3\\ 1&-1&-2&3 \end{pmatrix}

然后调用linprog函数求解即可:

1
2
3
4
5
6
7
8
9
from scipy import optimize
import numpy as np

c = np.array([1,2,3,4,1,2,3,4]).transpose()
A = np.array([[1,-1,-1,1],[1,-1,1,-3],[1,-1,-2,3]])
b = np.array([-2,-1,-1/2])

res = optimize.linprog(c=f,A_ub=np.hstack((A,-A)),b_ub=b,bounds=np.array([[0,None]]*8))
print(res)

目标函数含最值

如求解minxi{maxyixiyi}\min\limits_{x_i}\{\max\limits_{y_i}|x_i-y_i|\},可以取z=maxyixiyiz = \max\limits_{y_i}|x_i-y_i|

以上问题则转化为:

minzs.t.{xiyizyixiz\min z\\ s.t.\begin{cases} x_i-y_i \leq z\\ y_i-x_i \leq z \end{cases}

即可使用一般线性规划求解。

常见模型之投资的收益与风险

问题描述

市场上有nn种资产si(i=1,2,,n)s_i(i=1,2,…,n)可以选择,现用数额为M的相当大的资金作一个时期的投资。这nn种资产在这一时期内购买sis_i的平均收益率为rir_i,风险损失率为qiq_i,投资越分散,总风险越少,总体风险可用投资的sis_i中最大的一个风险来度量。

购买sis_i时要付交易费,费率为pip_i,当购买额不超过给定值uiu_i时,交易费按购买uiu_i计算。另,假定同期银行存款利率是r0r_0,既无交易费又无风险(r0=5%)(r_0=5\%)

投资的相关数据(n=4)(n=4)

sis_i ri/%r_i/\% qi/%q_i/\% pi/%p_i/\% ui/rmbu_i/\text{rmb}
s1s_1 28 2.5 1 103
s2s_2 21 1.5 2 198
s3s_3 23 5.5 4.5 52
s4s_4 25 2.6 6.5 40

符号规定与基本假设

  • 符号规定
    • sis_i代表第ii种投资项目,特别地,s0s_0代表存入银行;
    • p0=q0=0p_0=q_0=0
    • xix_i表示投入sis_i的资金;
    • aa代表投资风险度;
    • QQ代表总体收益。
  • 基本假设
    • 投资数额MM相当大,且远大于uiu_i;(部分特殊模型可能不成立,可以通过更改bounds参数处理);
    • sis_i之间相互独立;
    • 投资期间ri,pi,qir_i,p_i,q_i保持不变;
    • a,Qa,Q只受ri,pi,qir_i,p_i,q_i影响。

模型分析与建立

总体风险

按照题意,总体风险可用投资的sis_i中最大的一个风险来度量,即:

maxqixi\max q_ix_i

总体收益

总体收益为各项投资的收益之和减去投资手续费之和,其中手续费为分段函数:

p={piuixiuipixixi>uip = \begin{cases} p_iu_i & x_i \leq u_i\\ p_ix_i & x_i > u_i \end{cases}

由基本假设可知,M>>piuiM>>p_iu_i,故可假设xi>uix_i > u_i恒成立,则总体收益为:

Q=(ripi)xiQ = \sum(r_i-p_i)x_i

建立模型

要使收益尽可能大,风险尽可能小,需要建立一个多目标线性规划模型:

{max(ripi)ximinmaxqixis.t.{(1+pi)xi=Mxi0\begin{cases} \max \sum(r_i-p_i)x_i\\ \min \max q_ix_i \end{cases}\\ s.t.\begin{cases} \sum (1+p_i)x_i = M\\ x_i \geq 0 \end{cases}

模型简化

在实际投资中,不同公司侧重点不同,有的公司更看重风险,有的公司更看重收益。因此有以下三种简化方式:

固定风险水平,最大化收益

给定一个风险上限aa,令总体风险不超过aa的情况下,即qixiMa\frac{q_ix_i}{M}\leq a,使收益最大化,则模型可转化为如下单目标线性规划:

max(ripi)xis.t.{qixiMa(1+pi)xi=Mxi0\max \sum(r_i-p_i)x_i\\ s.t.\begin{cases} \frac{q_ix_i}{M}\leq a\\ \sum (1+p_i)x_i = M\\ x_i \geq 0 \end{cases}

固定收益水平,最小化风险

给定一个收益下限kk,使风险最小化:

minmaxqixis.t.{(1+pi)xi=M(ripi)xikxi0\min \max q_ix_i\\ s.t.\begin{cases} \sum (1+p_i)x_i = M\\ \sum(r_i-p_i)x_i \geq k\\ x_i \geq 0 \end{cases}

分别赋予权重

引入投资偏好系数s(0,1]s\in (0,1],对风险和收益分别赋予权重ss1s1-s

min{smaxqixi(1s)(ripi)xi}s.t.{(1+pi)xi=Mxi0\min \{s*\max q_ix_i-(1-s)*\sum(r_i-p_i)x_i\}\\ s.t.\begin{cases} \sum (1+p_i)x_i = M\\ x_i \geq 0 \end{cases}

代码实现(以模型一为例)

由于不同投资者有不同的风险度,故对风险度aa进行循环搜索并绘制图像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from scipy import optimize
import numpy as np
import matplotlib.pyplot as plt

a = 0
c = np.array([-0.05,-0.27,-0.19,-0.185,-0.185]).transpose()
A = np.array([
[0,0,0,0,0],
[0,0.025,0,0,0],
[0,0,0.015,0,0],
[0,0,0,0.055,0],
[0,0,0,0,0.026]
])
Aeq = np.array([[1,1.01,1.02,1.045,1.065]])
beq = np.array([1])
bounds = np.array([[0,None]]*5)

x = []
y = []

while a<0.05:
b = np.array([[a]]*5)
res = optimize.linprog(c=c, A_ub=A, b_ub=b, A_eq=Aeq, b_eq=beq, bounds=bounds)
x.append(a)
y.append(-res.fun)
a += 0.001

plt.figure(figsize=(10, 8),dpi=100)
plt.scatter(x,y)
plt.show()
plt.savefig('./figure.jpg')

结果分析:

xxgh-1

  • 风险大,收益也大
  • 投资分散时风险较小
  • 对并无偏好的投资者应选择曲线转折点投资,以达到最优效果