线性规划初步

字数: 2803

例题

例1.1 某机床厂生产甲、乙两种机床,每台销售后的利润分别为4千元与3千元。生产甲机床需用A、B机器加工,加工时间分别为每台2小时和1小时;生产乙机床需用A、B、C三种机器加工,加工时间为每台各一小时。若每天可用于加工的机器时数分别为A机器10小时、B机器8小时和C机器7小时,问该厂应生产甲、乙机床各几台,才能使总利润最大?

上述问题的数学模型:设该厂生产 $ x_{1} $ 台甲机床和 $ x_{2} $ 乙机床时总利润 $ z $ 最大,则 $ x_{1} $, $ x_{2} $ 应该满足:

$$ \begin{align} \max\quad & z = 4x_{1}+3x_{2} \tag{1.1} \\ \text{s.t.}\quad & \begin{cases} 2x_{1} + 2x_{2} \leq 10 \\ x_{1} + x_{2} \leq 8 \\ x_{2} \leq 7\\ x_{1}, x_{2} \geq 0 \end{cases} \tag{1.2} \end{align} $$

变量 $ x_{1} $, $ x_{2} $ 称之为决策变量,$ (1.1) $ 式被称为问题的目标函数,$ (1.2) $ 中几个不等式是问题的约束条件,记为 $ s.t. $ (subject to)

目标函数及约束条件均为线性函数,故称为线性规划问题。线性规划问题上在一组线性约束条件的限制下,求一线性目标函数最大或最小的问题。

概念

一般线性规划问题的数学标准型为:

$$ \begin{align} \max \quad & z = \sum_{j=1}^{n}c_{j}x_{j} \tag{1.3}\\ \text{s.t.}\quad & \begin{cases} \sum_{j=1}^{n} a_{ij}x_{j}\leq b_{i} \quad i = 1,2,...,m,\\ x_{j} \geq 0 \quad j = 1,2,...,n. \end{cases}\tag{1.4} \end{align} $$

其中 $ b_{i}>=0, i = 1, 2,…,m $

  • 可行解 满足约束条件 $ (1.4) $ 的解 $ x=[x_{1}, …, x_{n}]^{T} $ 称为线性规划问题的可行解,而使目标函数 $ (1.3) $ 达到最大值的可行解叫最优解。
  • 可行域 所有可行解构成的集合称为问题的可行域,记为 $ R $。

例如如下线性规划问题:

$$ \begin{align} \max \quad & z = 2x_{1} + 3x_{2} - 5x{3}\\ \text{s.t.}\quad & \begin{cases} x_{1} + x_{2} + x_{3} = 7\\ 2x_{1} - 5 x_{2} + x_{3} \geq 10\\ x_{1} + 3 x_{2} + x_{3} \leq 12\\ x_{1}, x_{2}, x_{3} \geq0 \end{cases} \end{align} $$

通过 python 的 pulp 库编写求解程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pulp

MyProbLP = pulp.LpProblem("LPProbDemo1", sense=pulp.LpMaximize)
x1 = pulp.LpVariable("x1", lowBound=0, upBound=7, cat="Continuous")
x2 = pulp.LpVariable("x2", lowBound=0, upBound=7, cat="Continuous")
x3 = pulp.LpVariable("x3", lowBound=0, upBound=7, cat="Continuous")

MyProbLP += 2 * x1 + 3 * x2 - 5 * x3

MyProbLP += x1 + x2 + x3 == 7
MyProbLP += 2 * x1 - 5 * x2 + x3 >= 10
MyProbLP += x1 + 3 * x2 + x3 <= 12
MyProbLP += x1 >= 0
MyProbLP += x2 >= 0
MyProbLP += x3 >= 0


MyProbLP.solve()
print("Status:", pulp.LpStatus[MyProbLP.status])
for v in MyProbLP.variables():
    print(v.name, "=", v.varValue)
print("F(x) = ", pulp.value(MyProbLP.objective))

最后输出:

1
2
3
4
5
Status: Optimal
x1 = 6.4285714
x2 = 0.57142857
x3 = 0.0
F(x) =  14.57142851

也就是说求得的最优解为 : $ x_{1}=6.4286, x_{2}=0.5714, x_{3}=0 $ 对应的最优值为 $ z = 14.5714 $

投资的收益和风险

问题提出

市场上有 $ n $ 种资产$ (i=1,2,…,n) $可以选择,现用数额为 $ M $ 的相当大的资金作一个时期的投资。这 n 种资产在这一时期内购买$ s_{i} $的平均收益率为$ r_{i} $,风险损失率为$ q_{i} $,投资越分散,总的风险越少,总体风险可用投资的$ s_{i} $中最大的一个风险来度量。
购买$ s_{i} $时要付交易费,费率为$ p_{i} $,当购买额不超过给定值$ u_{i} $,交易费按购买$ u_{i} $计算。另外,假定同期银行存款利率是$ r_{0} $,既无交易费又无风险 $ (r_{0}=5%) $。
已知 $ n=4 $ 时相关数据如表1.1

表 1.1 投资的相关数据

$ s_{i} $ $ r_{i}(%) $ $ q{i}(%) $ $ p_{i}(%) $ $ u_{i}(元) $
$ s_{1} $ 28 2.5 1 103
$ s_{2} $ 21 1.5 2 198
$ s_{3} $ 23 5.5 4.5 52
$ s_{4} $ 25 2.6 6.5 40

试给该公司设计一种投资组合方案,即用给定资金 M ,有选择地购买若干种资产或存银行生息,使净收益尽可能大,使总体风险尽可能小。

符号规定

  • $ s_{i} $:第 $ i $ 种投资项目如股票,债券等,$ i=0,1,…,n, $其中 $ s_{0} $ 指存入银行
  • $ r_{i}, p_i, q_i $ :分别表示 $ s_{i} $ 的平均收益率、交易费率、风险损失率 $ (i = 0,\dots,n $),且 $ p_0 = 0, q_0 = 0 $;
  • $ u_i $:$ s_i $ 的交易金额$ (i = 1,\dots,n) $;
  • $ x_i $:投资项目 $ s_i $ 的资金$ (i = 0,1,\dots,n) $;
  • $ a $:投资风险度;
  • $ Q $:总体收益。

该部分通常出现在投资组合优化或风险决策问题的模型建立之前。

模型建立

  1. 总体风险用所投资的 $ s_i $ 中最大的一个风险来衡量,即:
$$ \begin{aligned} max\{q_ix_i | i = 1,2,\dots,n\} \end{aligned} $$
  1. 购买 $ s_i(i=1,\dots,n) $ 所付交易费是一个分段函数:
$$ \begin{aligned} \text{交易费}= \begin{cases} p_ix_i, \quad x_i > u_i\\ p_iu_i, \quad x_i\le u_i\\ \end{cases} \end{aligned} $$

而题目所给的定值 $ u_i $ (单位:元)相对总投资 $ M $ 很少,$ p_iu_i $ 更小,这样购买 $ s_i $ 的净收益可简化为 $ (r_i-p_i)x_i $。(将交易费简化为线性,忽略了 $ u_i $)

  1. 要使净收益尽可能大,总体风险尽可能小,这是一个多目标规划模型。

目标函数为:

$$ \begin{aligned} \begin{cases} \max \sum_{i=0}^{n}(r_i-p_i)x_i,\\ \min \{\max_{1\le i \le n}\{q_ix_i\}\}\\ \end{cases} \end{aligned} $$

约束条件为:

$$ \begin{aligned} \begin{cases} \sum_{i=0}^{n}(1+p_i)x_i=M,\\ x_i\ge 0, i = 0,1,\dots,n. \end{cases} \end{aligned} $$

模型简化

在实际投资中,投资者承受风险的程度不一样,若给定风险一个界限 $ a $,使最大的一个风险率为 $ a $,即 $ \frac{q_ix_i}{M}\le a (i=1,\dots,n) $,可找到相应的投资方案,这样把多目标规划变成一个目标的线性规划:

固定风险水平,优化收益:

$$ \begin{align*} \max & \sum^{n}_{i=0}(r_i-p_i)x_i \\ \text{s.t.} \quad & \begin{cases} \frac{q_ix_i}{M} \le a, i=1, \dots,n\\ \sum^{n}_{i=0}(1+p_i)x_i=M, x_i\ge 0, i=0,1,\dots ,n. \end{cases} \end{align*} $$

在此基础得到 python 代码:

  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import pulp
import matplotlib.pyplot as plt

# ---------- 数据准备 ----------
# 投资项目数量(不包括银行存款)
n = 4

# 银行存款 s0 的参数
r0 = 0.05   # 年收益率 5%
p0 = 0.0    # 交易费率
q0 = 0.0    # 风险损失率

# 四个投资项目 (s1~s4)
data = [
    {'r': 0.27, 'p': 0.01, 'q': 0.025},   # s1
    {'r': 0.19, 'p': 0.02, 'q': 0.015},   # s2
    {'r': 0.185, 'p': 0.045, 'q': 0.055}, # s3
    {'r': 0.185, 'p': 0.065, 'q': 0.026}  # s4
]

# 总资金 M
M = 1.0

# 风险界限 a (投资者设定的最大风险比例)
# 例如 a = 0.05 表示最大允许损失占总资金的 5%
a = 0.05

# ---------- 建立模型并求解 ----------
def solve_for_risk_limit(risk_limit: float) -> tuple[str, list[float], float, float]:
    """
    在给定风险上限下求解最优投资方案。

    Parameters
    ----------
    risk_limit : float
        风险上限 a,表示最大允许风险损失占总资金的比例。

    Returns
    -------
    status : str
        求解状态("Optimal" 表示最优解, "Infeasible" 表示无可行解等)。
    values : list[float]
        决策变量 [x0, x1, x2, x3, x4],依次为银行存款和 4 个投资项目的资金分配额。
    net_return : float
        最大净收益,即目标函数值 r0*x0 + Σ(r_i - p_i)*x_i。
    risk_ratio : float
        组合实际风险比例,即 Σ(q_i * x_i) / M。
    """
    # 创建最大化问题
    model = pulp.LpProblem("Fixed_Risk_Optimization", pulp.LpMaximize)

    # 决策变量 x_i (i = 0,...,n)
    # x0: 银行存款资金; x1~x4: 四个投资项目的资金
    x = [pulp.LpVariable(f"x{i}", lowBound=0, cat='Continuous') for i in range(n+1)]

    # 目标函数: 最大化净收益
    # 银行存款净收益 = r0 * x0
    # 每个投资项目净收益 = (r_i - p_i) * x_i
    objective = r0 * x[0] + pulp.lpSum((data[i]['r'] - data[i]['p']) * x[i+1] for i in range(n))
    model += objective

    # 约束1: 风险约束 (对每个投资项目 i = 1..n)
    # q_i * x_i <= a * M
    for i in range(n):
        model += data[i]['q'] * x[i+1] <= risk_limit * M

    # 约束2: 资金总量约束
    # x0 + sum_{i=1}^n (1 + p_i) * x_i = M
    model += x[0] + pulp.lpSum((1 + data[i]['p']) * x[i+1] for i in range(n)) == M

    # 求解
    solver = pulp.PULP_CBC_CMD(msg=False)
    model.solve(solver)

    status = pulp.LpStatus[model.status]
    values = [v.varValue if v.varValue is not None else 0.0 for v in x]
    net_return = pulp.value(objective) if status == "Optimal" else 0.0
    total_risk = sum(data[i]['q'] * values[i+1] for i in range(n))
    risk_ratio = total_risk / M if M else 0.0
    return status, values, net_return, risk_ratio


# ---------- 单点求解 ----------
status, values, net_return, risk_ratio = solve_for_risk_limit(a)

# ---------- 输出结果 ----------
print("求解状态:", status)
if status == "Optimal":
    print("\n最优投资方案 (资金分配):")
    print(f"银行存款 s0 : {values[0]:.2f} 元")
    for i in range(n):
        print(f"投资 s{i+1}    : {values[i+1]:.2f} 元")
    print(f"\n最大总收益 (净): {net_return:.6f}")
    print(f"组合风险比例(总损失/M): {risk_ratio:.6f}")
    # 验证总资金
    total = values[0] + sum((1+data[i]['p']) * values[i+1] for i in range(n))
    print(f"实际使用总资金: {total:.6f} (应等于 {M})")
else:
    print("未找到最优解,请检查约束或参数 a 是否过小。")

# ---------- 风险-收益关系图 ----------
# 计算不同风险上限下的最优收益 (a 从 0 到 <0.05, 步长 0.001)
a_values = [i * 0.001 for i in range(50)]  # 生成风险上限序列
returns = []  # 存储每个风险上限对应的最优收益
for risk_limit in a_values:  # 遍历风险上限
    status, _, net_return, _ = solve_for_risk_limit(risk_limit)  # 求解对应的最优收益
    if status == "Optimal":  # 判断是否得到最优解
        returns.append(net_return)  # 保存最优收益
    else:
        returns.append(0.0)  # 无解时用 0 占位

plt.figure(figsize=(7, 4.5))  # 创建画布并设置大小
plt.plot(a_values, returns, marker='*', linestyle='None')  # 绘制散点图
plt.title("Risk-Return")  # 图标题
plt.xlabel("a")  # x 轴标签
plt.ylabel("Q")  # y 轴标签
plt.grid(True, linestyle='--', alpha=0.4)  # 添加网格线
plt.tight_layout()  # 自动调整边距
plt.show()  # 显示图像

输出图表: