关键词:Python、调包、线性规划、指派问题、运输问题、pulp、混合整数线性规划(MILP)

注:此文章是线性规划的调包实现,具体步骤原理请搜索具体解法。

  本文章的各个问题可能会采用多种调用方法,为什么?因为这些包各有特点,有些语法特别像matlab,只要稍稍改变即可达成代码交换;而有些包利用了python本身的特性,在灵活度与代码的可读性上更高。我认为这些包各有优劣,各位各持所需吧。

  看了本文章能做到什么?你可以在本文章内学到线性规划的几个问题的求解方式,并学会如何用pulp包解决线性规划问题。无论是整数规划(Integer Program)、01规划(Binary Program)还是混合整数线性规划(MILP),你都可以得到很好的解题方法。

一、线性规划

该问题引用自《数学建模算法与应用-司守奎》第一章线性规划 3.线性规划
包的具体使用可参考scipy官网

  求解最普通的线性规划问题:

scipy调包代码

import numpy as np

z = np.array([2, 3, 1])

a = np.array([[1, 4, 2], [3, 2, 0]])

b = np.array([8, 6])

x1_bound = x2_bound = x3_bound =(0, None)

from scipy import optimize

res = optimize.linprog(z, A_ub=-a, b_ub=-b,bounds=(x1_bound, x2_bound, x3_bound))

print(res)

#output:
# fun: 7.0
# message: 'Optimization terminated successfully.'
# nit: 2
# slack: array([0., 0.])
# status: 0
# success: True
# x: array([0.8, 1.8, 0. ])

  注意,此函数和matlab的linprog有很多相似之处。
  首先默认就是求解最小值,如果想要求最大值,需要对目标函数的各参数取反(既对z取反),并在得出的结果(func)前取反。
  并且所有约束条件都为≤,因此例子中传入参数是前面都加了负号。
  bound为边界的二元元组,None时为无边界。
  如果存在类似这种情况,可以:

A_eq = [[1,2,4]]
b_eq = [101]

并在linprog中传入。
  得出的结果为scipy.optimize.optimize.OptimizeResult(优化结果)类型,是类似字典的结构,例如想要优化结果代入目标函数的值,可以res.fun或res['fun']这样取值。

pulp调包代码

import pulp
#目标函数的系数
z = [2, 3, 1]
#约束
a = [[1, 4, 2], [3, 2, 0]]
b = [8, 6]
#确定最大化最小化问题,最大化只要把Min改成Max即可
m = pulp.LpProblem(sense=pulp.LpMinimize)
#定义三个变量放到列表中
x = [pulp.LpVariable(f'x{i}', lowBound=0) for i in [1,2,3]]
#定义目标函数,lpDot可以将两个列表的对应位相乘再加和
#相当于z[0]*x[0]+z[1]*x[0]+z[2]*x[2]
m += pulp.lpDot(z, x) #设置约束条件
for i in range(len(a)):
m += (pulp.lpDot(a[i], x) >= b[i])
#求解
m.solve()
#输出结果
print(f'优化结果:{pulp.value(m.objective)}')
print(f'参数取值:{[pulp.value(var) for var in x]}') #output:
#优化结果:7.0
#参数取值:[2.0, 0.0, 3.0]

  每一步的说明已经注释在代码中,可以看到输出结果,两者的变量取值并不一致,但代入目标函数的结果都是一样的。
  同样的,如果存在类似这种情况,可以:

A_eq = [1,2,4]
b_eq = 101
m += (pulp.lpDot(A_eq, x) == b_eq)

二、运输问题

   某商品有个产地、个销地,各产地的产量分别为,各销地的 需求量分别为。若该商品由产地运到销地的单位运价为,问应该如何调 运才能使总运费最省?
   引入变量,其取值为由产地运往销地的该商品数量,数学模型为 :
例题:

 
 

pulp调包代码

import pulp
import numpy as np
from pprint import pprint def transportation_problem(costs, x_max, y_max): row = len(costs)
col = len(costs[0]) prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMaximize) var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)] flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] prob += pulp.lpDot(flatten(var), costs.flatten()) for i in range(row):
prob += (pulp.lpSum(var[i]) <= x_max[i]) for j in range(col):
prob += (pulp.lpSum([var[i][j] for i in range(row)]) <= y_max[j]) prob.solve() return {'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]}

然后构造参数传递进去:

if __name__ == '__main__':
costs = np.array([[500, 550, 630, 1000, 800, 700],
[800, 700, 600, 950, 900, 930],
[1000, 960, 840, 650, 600, 700],
[1200, 1040, 980, 860, 880, 780]]) max_plant = [76, 88, 96, 40]
max_cultivation = [42, 56, 44, 39, 60, 59]
res = transportation_problem(costs, max_plant, max_cultivation) print(f'最大值为{res["objective"]}')
print('各变量的取值为:')
pprint(res['var']) #output:
#最大值为284230.0
#各变量的取值为:
#[[0.0, 0.0, 6.0, 39.0, 31.0, 0.0],
# [0.0, 0.0, 0.0, 0.0, 29.0, 59.0],
# [2.0, 56.0, 38.0, 0.0, 0.0, 0.0],
# [40.0, 0.0, 0.0, 0.0, 0.0, 0.0]]

三、指派问题

该问题引用自《数学建模算法与应用-司守奎》第一章线性规划 3.指派问题
调包解决方法参考https://blog.csdn.net/your_answer/article/details/79160045
可参考官网https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linear_sum_assignment.html

  拟分配n人去干项工作,没人干且仅干一项工作,若分配第人去干第项工作,需花费单位时间,问应如何分配工作才能使公认花费的总时间最少?
假设指派问题的系数矩阵为:  引入变量,若分配工作,则取,否则取,上述指派问题的数学模型为  指派问题的可行解用矩阵表示,每行每列有且只有一个元素为1,其余元素均为0。

调用scipy解决

  原书使用匈牙利算法解决的,在这里我们用scipy的优化模块解决

import numpy as np
from scipy.optimize import linear_sum_assignment

  引入包,linear_sum_assignment是专门用于解决指派问题的模块。

efficiency_matrix = np.array([
[12,7,9,7,9],
[8,9,6,6,6],
[7,17,12,14,12],
[15,14,6,6,10],
[4,10,7,10,6]
]) row_index, col_index=linear_sum_assignment(efficiency_matrix)
print(row_index+1)
print(col_index+1)
print(efficiency_matrix[row_index,col_index]) #output:
#[1 2 3 4 5]
#[2 3 1 4 5]
#[7 6 7 6 6]

  定义了开销矩阵(指派问题的系数矩阵)efficiency_matrix,传入linear_sum_assignment,结果返回的是最优指派的行和列,例如第一行选择第二列,意为:将第一个人派往第二个工作。而根据numpy.array的性质,传入行和列就会返回行列所对应的值,即为输出的第三列

print(efficiency_matrix[row_index, col_index].sum())
#output:
# 32

  对其求和,即可得到指派问题的最小时间。

调用pulp解决

  先定义通用解决方法,其中的flatten是递归展开列表用的。

def assignment_problem(efficiency_matrix):
row = len(efficiency_matrix)
col = len(efficiency_matrix[0]) flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] m = pulp.LpProblem('assignment', sense=pulp.LpMinimize)
var_x = [[pulp.LpVariable(f'x{i}{j}', cat=pulp.LpBinary) for j in range(col)] for i in range(row)] m += pulp.lpDot(efficiency_matrix.flatten(), flatten(var_x)) for i in range(row):
m += (pulp.lpDot(var_x[i], [1]*col) == 1) for j in range(col):
m += (pulp.lpDot([var_x[i][j] for i in range(row)], [1]*row) == 1) m.solve() print(m) return {'objective':pulp.value(m.objective), 'var': [[pulp.value(var_x[i][j]) for j in range(col)] for i in range(row)]}

  然后定义矩阵,输入求解

efficiency_matrix = np.array([
[12, 7, 9, 7, 9],
[8, 9, 6, 6, 6],
[7, 17, 12, 14, 9],
[15, 14, 6, 6, 10],
[4, 10, 7, 10, 9]
]) res = assignment_problem(efficiency_matrix)
print(f'最小值{res["objective"]}')
print(res['var']) #output
#最小值32.0
#[[0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0]]

四、pulp的使用方式

基本使用

  可以看出,pulp在线性规划这一部分,有更多的通用性,编写程序更自由。前面的例子已经足够丰富了,但是如果读到这里还不去清楚pulp的使用方式的话……可以去百度一下,我这里也简单讲一讲。

  首先是定义一个问题,第一个参数为问题的名称,不过并非必要的参数,而通过sense参数可决定优化的是最大值还是最小值

prob = pulp.LpProblem('problem name', sense=pulp.LpMinimize)

  然后是定义变量:

x0 = pulp.LpVariable('x0', lowBound=0, upBound=None, cat=pulp.LpInteger)
x1 = pulp.LpVariable('x1', lowBound=0, upBound=None, cat=pulp.LpInteger)
x2 = pulp.LpVariable('x2', lowBound=0, upBound=None, cat=pulp.LpInteger)

  这里定义了三个变量,第一个参数为变量名,lowBound 、upBound为上下限,cat为变量类型,默认为连续变量,还可以设为离散变量或二值变量,具体怎么设置?
如上述代码所示,pulp.LpInteger为离散变量,pulp.LpBinary为二值变量,同时也可以传入'Integer'字符串的方式指明变量类型。从上面几个问题的代码可以看出,我几乎没有单个定义变量,而是批量定义的。
  然后是添加目标函数:

prob += 2*x0-5*x1+4*x2

  只要让问题(prob)直接加变量的表达式即可添加目标函数。
  再之后是添加约束:

prob += (x0+x1-6*x2 <= 120)

  只要让问题(prob)直接加变量的判断式即可添加约束

prob.solve()

  调用solve方法解出答案,如果省去这一步,后面的变量和结果值都会显示None。

print(pulp.value(prob.objective))
print(pulp.value(x0))

打印优化结果,并显示当优化达到结果时x0的取值。

思考程序本质

  problem对象是如何通过不断加来获得目标函数和约束的?熟悉python或者c++的朋友可能会想到一个词:操作符重载。
  没错,就是这么实现的,上述的对象几乎都实现了不同的重载。
  首先是Problem对象prob,全名pulp.pulp.LpProblem;当打印输出(print)时,会打印出问题名,当不断增加目标函数、约束时,也会随着print输出;而它的__add__一定是被定义过了,我们先说其他对象。
  当我们定义一个变量时,它的类型是pulp.pulp.LpVariable,当我们对这些变量和其他变量做加法、和其他常数做乘法时,它会返回一个新的对象,经检测,这个新对象的类名叫pulp.pulp.LpAffineExpression,顾名思义,叫做关系表达式;如果print,会打印这个关系表达式。
  而如果对关系表达式做出:>=、<=、==时,会返回新的对象,类名叫做pulp.pulp.LpConstraint,即约束对象;如果print,会打印这个约束。
  将关系表达式(pulp.pulp.LpAffineExpression)与问题(pulp.pulp.LpProblem)相加时,会返回新的问题对象,并且新的问题对象会将这个关系表达式作为目标函数。
  将约束对象(pulp.pulp.LpConstraint)与问题(pulp.pulp.LpProblem)相加时,会返回新的问题对象,并且这个新的问题对象会多出约束对象所代表的约束条件。
  调用问题对象的solve方法,解出线性规划的解。
  访问问题对象的objective成员变量,会得到目标函数(关系表达式对象)。
  调用pulp的value方法,将获得对变量代入值的结果,如果是关系表达式对象,将获得优化结果;如果是变量对象,将获得优化结果达到时的变量取值;如果是None,说明你忘调用solve了。

  这个包的使用,我是在google中查到的,如果有兴趣,可以去原地址看看,拥有更多的应用以及其他包的调用https://qiita.com/SaitoTsutomu/items/bfbf4c185ed7004b5721
  也可以去bilibili看看相关的视频https://www.bilibili.com/video/av18818604?from=search&seid=17463693264345909709

作者:crossous
链接:https://www.jianshu.com/p/9be417cbfebb
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

【数学建模】线性规划各种问题的Python调包方法的更多相关文章

  1. python 版 mldivide matlab 反除(左除)《数学建模算法与程序》Python笔记

    今天在阅读数学建模的时候看到了差分那章 其中有一个用matlab求线性的代码,这里我贴出来 这里我送上 Python代码 In [39]: import numpy as np ...: from s ...

  2. Python小白的数学建模课-03.线性规划

    线性规划是很多数模培训讲的第一个算法,算法很简单,思想很深刻. 要通过线性规划问题,理解如何学习数学建模.如何选择编程算法. 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛 ...

  3. Python小白的数学建模课-A3.12 个新冠疫情数模竞赛赛题与点评

    新冠疫情深刻和全面地影响着社会和生活,已经成为数学建模竞赛的背景帝. 本文收集了与新冠疫情相关的的数学建模竞赛赛题,供大家参考,欢迎收藏关注. 『Python小白的数学建模课 @ Youcans』带你 ...

  4. Python小白的数学建模课-12.非线性规划

    非线性规划是指目标函数或约束条件中包含非线性函数的规划问题,实际就是非线性最优化问题. 从线性规划到非线性规划,不仅是数学方法的差异,更是解决问题的思想方法的转变. 非线性规划问题没有统一的通用方法, ...

  5. Python小白的数学建模课-A1.2021年数维杯C题(运动会优化比赛模式探索)探讨

    Python小白的数学建模课 A1-2021年数维杯C题(运动会优化比赛模式探索)探讨. 运动会优化比赛模式问题,是公平分配问题 『Python小白的数学建模课 @ Youcans』带你从数模小白成为 ...

  6. Python小白的数学建模课-04.整数规划

    整数规划与线性规划的差别只是变量的整数约束. 问题区别一点点,难度相差千万里. 选择简单通用的编程方案,让求解器去处理吧. 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛达 ...

  7. Python小白的数学建模课-A1.国赛赛题类型分析

    分析赛题类型,才能有的放矢. 评论区留下邮箱地址,送你国奖论文分析 『Python小白的数学建模课 @ Youcans』 带你从数模小白成为国赛达人. 1. 数模竞赛国赛 A题类型分析 年份 题目 要 ...

  8. Python小白的数学建模课-05.0-1规划

    0-1 规划不仅是数模竞赛中的常见题型,也具有重要的现实意义. 双十一促销中网购平台要求二选一,就是互斥的决策问题,可以用 0-1规划建模. 小白学习 0-1 规划,首先要学会识别 0-1规划,学习将 ...

  9. Python小白的数学建模课-06 固定费用问题

    Python 实例介绍固定费用问题的建模与求解. 学习 PuLP工具包中处理复杂问题的快捷使用方式. 『Python小白的数学建模课 @ Youcans』带你从数模小白成为国赛达人. 前文讲到几种典型 ...

随机推荐

  1. 3. Dictionaries and Sets

    1. Generic Mapping Types The collections.abc module provides the Mapping and MutableMapping ABCs to ...

  2. 算法9-----输出全排列(递归)---移除K个数,剩下最小数。

    1.题目:给定一个字符串,输出所有的字典序. 如: 输入字符串:'ac',输出:['ac','ca'] 输入字符串:‘abc' ,输出:['abc','acb','bac','bca','cab',' ...

  3. vscode入门记

    蒟蒻也是第一次从Dev转过来呢, 因为vsc界面,实用性,美观性,以及稳定性(Dev那注释中的乱码不想吐槽.)都比Dev强,... fzy: _GC: 扶苏: water_lift: ych: 不想做 ...

  4. 软件测试能满足测试的sql

    作为一个软件测试工程师,我们在测试过程中往往需要对数据库数据进行操作,但是我们的操作大多以查询居多,有时会涉及到新增,修改,删除等操作,所以我们其实并不需要对数据库的操作有特别深入的了解,以下是我在工 ...

  5. python中else与finally的总结

    1.else的用法 对try...except的补充: else子句的使用比在子句中添加其他代码更好,try因为它避免了意外捕获由try... except语句保护的代码未引发的异常. for arg ...

  6. BZOJ1101——莫比乌斯函数&&入门

    题目 链接 有$50000$次查询,对于给定的整数$a,b$和$d$,有多少正整数对$x$和$y$,满足$x \leq a$,$y \leq b$,并且$gcd(x, y)=d$.$1 \leq k ...

  7. 防止sql注入的参数化查询

    参数化查询为什么能够防止SQL注入 http://netsecurity.51cto.com/art/201301/377209.htm OleDbDataAdapter Class http://m ...

  8. js 获取滚动条的高度

    function getScrollTop() { var scroll_top = 0; if (document.documentElement && document.docum ...

  9. 下载使用IDE练习插件

    安装IDE练习插件 启动Eclipse,选择菜单“Help”-“Install New Software...”,在打开的对话框中: 点击“Add”,对Name填写一个任意的名称,例如“Java Pr ...

  10. C++类中函数(构造函数、析构函数、拷贝构造函数、赋值构造函数)

    [1]为什么空类可以创建对象呢? 示例代码如下: #include <iostream> using namespace std; class Empty { }; void main() ...