用数学规划的方式求解优化问题

2023-05-16

本文介绍如何用数学语言对实际中的优化问题进行建模. 通过建立数学模型, 我们利用现成的求解器可以便捷地计算出最优解(或可行解).

运输问题

考虑三个粮食储量分别是100, 200, 300的仓库 (单位:吨, 下文省略). 我们需要把粮食运送给4个客户, 其需求分别是: 120, 60, 270, 150.

仓库到客户的单位运输成本用矩阵 C C C描述:
[ 350 200 300 250 220 330 300 270 215 230 290 240 ] \begin{aligned} \begin{bmatrix} 350 & 200 & 300 & 250 \\ 220 & 330 & 300 & 270 \\ 215 & 230 & 290 & 240 \\ \end{bmatrix} \end{aligned} 350220215200330230300300290250270240
其中行代表仓库, 列代表客户. 矩阵中的每一个值代表对应的仓库到客户的单位运输成本. 我们的目标最小化总的运输成本.

下面我们用数学语言描述该问题.

输入

  • 仓库的供给量 s i s_i si, i = 1 , 2 , . . . , m i=1, 2, ... ,m i=1,2,...,m, 其中 m m m是仓库总数
  • 客户的需求量 d j d_j dj, j = 1 , 2 , . . . , n j= 1, 2, ..., n j=1,2,...,n, 其中 n n n是客户总数
  • 仓库 i i i到客户 j j j的单位运输成本是 c i , j c_{i, j} ci,j

输出

  • 需要计算仓库 i i i到客户 j j j的运输量 x i , j x_{i,j} xi,j

下面我们写出问题的目标和约束.

目标是最小化总的运输成本, 即
min ⁡ ∑ i , j c i j x i j . \min \sum_{i,j}c_{ij}x_{ij}. mini,jcijxij.

我们需要满足的约束条件有两个:

  • 每个仓库的出库量不能超过其供给量: ∑ j x i j ≤ a i \sum_{j} x_{ij} \leq a_i jxijai, ∀ i \forall i i
  • 每个客户的需求应该被满足: ∑ i x i j = d j \sum_{i} x_{ij} = d_j ixij=dj, ∀ j \forall j j

综上所述, 我们可以把运输问题用线性规划(Linear Programming)来表示.

min ⁡   ∑ i j c i j x i j s.t.  ∑ j x i j ≤ a i , ∀ i ∑ i x i j = d j , ∀ j x i j ≥ 0 , ∀ i , j . \begin{aligned} \min~& \sum_{ij}c_{ij} x_{ij} \\ \text{s.t. } & \sum_{j} x_{ij} \leq a_i, \forall i \\ & \sum_{i} x_{ij} = d_j, \forall j \\ & x_{ij} \geq 0, \forall i, j. \end{aligned} min s.t. ijcijxijjxijai,iixij=dj,jxij0,i,j.

标准实践

为了更加直观地写出数学模型, 我们可以总结一份标准的指南. 它包含四个基本步骤:

  • 指标(Indices)
    指标的作用是主要为了简化记号. 以上述运输问题为例, 我们的指标有 i i i j j j, 其中 i i i代表仓库, j j j代表客户.

  • 参数(Parameters)
    参数是问题的输入. 以上述运输问题为例, 我们的参数是: 供给量( s i s_i si), 需求量( d j ) d_j) dj), 单位运输成本 c i , j c_{i,j} ci,j.

  • 决策变量(Decision Variables)
    决策变量是算法的输出.

  • 优化目标(Objective)
    一般是最小化或最大化一个目标函数. 在某些情况下, 问题只需要找到一个可行解, 因此也可以不指定优化目标.

  • 约束(Constraints)
    用等式或不等式描述解的限制.

求解规划

常用的商用求解器有Gruobi和CPLEX(可申请教育和学术的lisense). 商用求解器功能强大, 能求解多种类型的规划问题, 例如整数规划, 混合整数规划, 二次规划等. 免费的求解器有Google的ORtools, 它把一些开源的求解器做了集成, 求解速度虽然比不上商用求解器, 实际中也能满足很多业务需求.

求解方式有两种:

第一种是直接用商用求解器提供的IDE. 按照求解器的建模语法把模型写出来, 然后求解. 建模语法的好处是非常贴近公式化的描述, 所见即所得.

第二种是调用求解器提供的API, 初始化参数, 约束, 目标, 然后求解.

本文我们使用开源工具ORtools求解(基本的教程请自行google,需要翻墙)

Python实现

模型

# model.py

from ortools.linear_solver import pywraplp
import numpy as np


class TransportModel(object):

    def __init__(self, a, d, C):
        """
        :param a: 供给量(m维向量), m代表仓库数量
        :param d: 需求量(n维向量), n代表客户数量
        :param C: 单位运输成本(m*n维矩阵), C[i][j]代表仓库i到客户j的单位运输成本
        """
        self._solver = pywraplp.Solver('TransportModel',
                                       pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)
        self._a = a
        self._d = d
        self._C = C
        self._m = len(self._a)  # 仓库数量
        self._n = len(self._d)  # 客户数量
        self._x = None  # 决策变量
        self._solution_x = None  # 计算结果
        self._obj_val = None  # 目标函数值

    def _init_decision_variables(self):
        self._x = [
            # 0 <= x[i][j] <= infinity
            [self._solver.NumVar(0, self._solver.infinity(), "x[%d][%d]" % (i, j))
             for j in range(self._n)] for i in range(self._m)
        ]

    def _init_constraints(self):
        # 每个仓库的出库量不能超过其供给量
        # sum(x[i][j]) <= a[i], over j
        for i in range(self._m):
            ct = self._solver.Constraint(0, self._a[i])
            for j in range(self._n):
                ct.SetCoefficient(self._x[i][j], 1)
        # 每个客户的需求应该被满足
        # sum(x[i][j]) == b[j], over i
        for j in range(self._n):
            ct = self._solver.Constraint(self._d[j], self._d[j])
            for i in range(self._m):
                ct.SetCoefficient(self._x[i][j], 1)

    def _init_objective(self):
        obj = self._solver.Objective()
        for i in range(self._m):
            for j in range(self._n):
                obj.SetCoefficient(self._x[i][j], self._C[i][j])
        obj.SetMinimization()

    def solve(self):
        self._init_decision_variables()
        self._init_constraints()
        self._init_objective()
        self._solver.Solve()
        # 求解器返回的解
        self._solution_x = [
            [self._x[i][j].solution_value() for j in range(self._n)]
            for i in range(self._m)
        ]
        # sum(C[i][i] * x[i][j]) over i,j
        self._obj_val = np.sum(np.array(self._C) * np.array(self._solution_x))

    def print_result(self):
        print("最优值 = ", self._obj_val)
        print("最优解 x = ")
        print(np.array(self._solution_x))

主函数

# main.py

from data import a, d, C  # 运输问题实例
from model import TransportModel


if __name__ == '__main__':
    tm = TransportModel(a, d, C)
    tm.solve()
    tm.print_result()

完整代码: 运输问题

数独(Sudoku)

把数字1-9填入下图的空格子中, 且满足如下三个条件:

  1. 每个区块 (图中灰色方框包含的3$\times$3小格子)包含数字1-9
  2. 每行包含数字1-9
  3. 每列包含数字1-9

我们通过数学规划的方式求解该问题.

指标

  • n n n – 填入的数字, n ∈ { 1 , 2 , . . . , 9 } n \in \{1, 2, ..., 9 \} n{1,2,...,9}
  • i i i – 第 i i i行区块, 区块一共三行, 因此 i ∈ { 1 , 2 , 3 } i \in \{1, 2, 3\} i{1,2,3}
  • j j j – 第 j j j行区块, 区块一共三列, 因此 j ∈ { 1 , 2 , 3 } j \in \{1, 2, 3\} j{1,2,3}
  • p p p – 区块中元素的行, 每个区块包含三行, 因此$p \in {1,2,3 } $
  • q q q – 区块中元素的列, 每个区块包含三列 q ∈ { 1 , 2 , 3 } q \in \{ 1, 2, 3\} q{1,2,3}

参数

  • a i , j , p , q , n ∈ { 0 , 1 } a_{i,j,p,q,n} \in \{ 0, 1\} ai,j,p,q,n{0,1} – 考虑第 i i i j j j列的区块, 它的 i i i j j j列是否数字 n n n

决策变量

  • x i , j , p , q , n ∈ { 0 , 1 } x_{i,j,p,q,n} \in \{ 0, 1\} xi,j,p,q,n{0,1} – 考虑第 i i i j j j列的区块, 它的 i i i j j j列是填入否数字 n n n

约束

  1. 已经存在的值不能修改.
    x i , j , p , q , n ≥ a i , j , p , q , n x_{i,j,p,q, n} \geq a_{i,j,p, q, n} xi,j,p,q,nai,j,p,q,n, ∀ i , j , p , q , n \forall i,j,p,q, n i,j,p,q,n
  2. 一个单元格同时只允许填入一个数字.
    ∑ n x i , j , p , q , n = 1 , ∀ i , j , p , q \sum_n x_{i,j,p,q,n} = 1, \forall i,j,p,q nxi,j,p,q,n=1,i,j,p,q
  3. 每个区块包含数字1-9.
    ∑ p , q x i , j , p , q , n = 1 , ∀ i , j , n \sum_{p, q} x_{i,j, p, q, n} = 1, \forall i, j, n p,qxi,j,p,q,n=1,i,j,n
  4. 每行包含数字1-9.
    ∑ j , q x i , j , p , q , n = 1 , ∀ i , p , n \sum_{j, q} x_{i,j,p,q,n} = 1, \forall i,p, n j,qxi,j,p,q,n=1,i,p,n
  5. 每列包含数字1-9.
    ∑ i , p x i , j , p , q , n = 1 , ∀ j , q , n \sum_{i, p} x_{i,j,p,q,n} = 1, \forall j,q, n i,pxi,j,p,q,n=1,j,q,n

综上所述, 我们的规划可以写成下面的整数规划(Integer Programming). 注意: 无优化目标.

min ⁡   0 s.t.  x i , j , p , q , n ≥ a i , j , p , q , n , ∀ i , j , p , q , n ∑ n x i , j , p , q , n = 1 , ∀ i , j , p , q ∑ p , q x i , j , p , q , n = 1 , ∀ i , j , n ∑ j , q x i , j , p , q , n = 1 , ∀ i , p , n ∑ i , p x i , j , p , q , n = 1 , ∀ j , q , n x i , j , p , q ∈ { 0 , 1 } . \begin{aligned} \min~& 0 \\ \text{s.t. } & x_{i,j,p,q,n} \geq a_{i,j,p,q,n}, \forall i, j,p,q, n \\ & \sum_n x_{i,j,p,q,n} = 1, \forall i,j,p,q \\ & \sum_{p, q} x_{i,j, p, q, n} = 1, \forall i, j, n \\ & \sum_{j, q} x_{i,j,p,q,n} = 1, \forall i,p, n \\ & \sum_{i, p} x_{i,j,p,q,n} = 1, \forall j,q, n \\ & x_{i,j,p,q} \in \{ 0,1\} . \end{aligned} min s.t. 0xi,j,p,q,nai,j,p,q,n,i,j,p,q,nnxi,j,p,q,n=1,i,j,p,qp,qxi,j,p,q,n=1,i,j,nj,qxi,j,p,q,n=1,i,p,ni,pxi,j,p,q,n=1,j,q,nxi,j,p,q{0,1}.

Python实现

模型

# model.py

from ortools.linear_solver import pywraplp
import numpy as np


class SudokuModel(object):

    def __init__(self, a):
        """
        :param a: Sudoku实例 
        """
        self._solver = pywraplp.Solver('SudokuModel',
                                       pywraplp.Solver.BOP_INTEGER_PROGRAMMING)
        self._a = a
        self._x = None  # 决策变量
        self._solution_x = None  # 计算结果

    def __init_decision_variables(self):
        self._x = np.empty((3, 3, 3, 3, 9)).tolist()
        for i in range(3):
            for j in range(3):
                for p in range(3):
                    for q in range(3):
                        for n in range(9):
                            # 已知数字不允许修改
                            # x[i][j][p][q][n] >= a[i][j][p][q][n]
                            self._x[i][j][p][q][n] \
                                = self._solver.IntVar(self._a[i][j][p][q][n], 1,
                                                      'x[%d][%d][%d][%d][%d]' % (i, j, p, q, n))

    def __init_constraints(self):
        # 一个单元格同时只允许填入一个数字
        # sum(x[i][j][p][q][n]) = 1, over n
        for i in range(3):
            for j in range(3):
                for p in range(3):
                    for q in range(3):
                        ct = self._solver.Constraint(1, 1)
                        for n in range(9):
                            ct.SetCoefficient(self._x[i][j][p][q][n], 1)
        # 每个区块包含数字1-9
        # sum(x[i][j][p][q][n]) = 1, over p, q
        for i in range(3):
            for j in range(3):
                for n in range(9):
                    ct = self._solver.Constraint(1, 1)
                    for p in range(3):
                        for q in range(3):
                            ct.SetCoefficient(self._x[i][j][p][q][n], 1)
        # 每行包含数字1-9
        # sum(x[i][j][p][q][n]) = 1, over j, q
        for i in range(3):
            for p in range(3):
                for n in range(9):
                    ct = self._solver.Constraint(1, 1)
                    for j in range(3):
                        for q in range(3):
                            ct.SetCoefficient(self._x[i][j][p][q][n], 1)
        # 每列包含数字1-9
        # sum(x[i][j][p][q][n]) = 1, over i, p
        for j in range(3):
            for q in range(3):
                for n in range(9):
                    ct = self._solver.Constraint(1, 1)
                    for i in range(3):
                        for p in range(3):
                            ct.SetCoefficient(self._x[i][j][p][q][n], 1)

    def solve(self):
        self.__init_decision_variables()
        self.__init_constraints()
        self._solver.Solve()
        self._get_solution_x()

    def _get_solution_x(self):
        self._solution_x = np.empty((3, 3, 3, 3))
        for i in range(3):
            for j in range(3):
                for p in range(3):
                    for q in range(3):
                        for n in range(9):
                            if self._x[i][j][p][q][n].solution_value() == 1:
                                self._solution_x[i][j][p][q] = n + 1

    def print_result(self):
        res = np.empty((9, 9))
        for i in range(3):
            for p in range(3):
                for j in range(3):
                    for q in range(3):
                        res[i*3+p][j*3+q] = self._solution_x[i][j][p][q]
        print(res)

主函数

# main.py

from model import SudokuModel
from data import a


if __name__ == '__main__':
    sm = SudokuModel(a)
    sm.solve()
    sm.print_result()

完整代码: Sudoku

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用数学规划的方式求解优化问题 的相关文章

随机推荐

  • 反解析Android APK

    重命名APK为zip 并解压到当前文件中 下载dex解析工具dex2jar link https github com pxb1988 dex2jar releases tag v2 2 SNAPSHOT 2021 10 31 解析文件命令
  • 大数加法越界处理

    大数越界处理 当数据很大时 xff0c 求中点的方法 i 43 j 2的结果有可能会超出 int 类型的取值范围 在此情况下 xff0c 我们需要换一种计算中点的写法 i 43 j 有可能超出 int 的取值范围 int m 61 i 43
  • idea使用断言

    测试程序 public class TestAssert public static void main String args String s1 61 null assert s1 61 null 默认assert不起作用 需开启 ve
  • 2018年面试总结 -- 多线程很重要,多总结很重要

    2018年面试总结 先后面试了Android Linux C C 43 43 QT 得到的结论有以一下几条 xff1a 1 多线程作为一个高级话题太重要了 几乎每种开发环境下都会问到 xff0c 无论是Android还是QT xff1b 2
  • 平常使用的小问题【Windows11开启不了热点,开启热点后连不上去】

    故障现象 xff1a xff08 1 xff09 连接设备一直显示obtaining ip address xff08 2 xff09 电脑没有显示连接的设备 解决措施 xff1a xff08 1 xff09 先转到设置 xff08 2 x
  • IOException: No such file or directory 问题解决

    文章目录 问题描述 xff1a 注册权限动态申请权限总结 xff1a 问题描述 xff1a Android开发 xff0c 在访问文件夹创建文件的时候 xff0c 报错IOException No such file or director
  • 2020(春)工程伦理MOOC期末考试答案

    单选题 多选题 判断题
  • Git中Fork使用

    GitLab或者GitHub中的Fork可以理解为一个物理副本 xff0c 用来管理代码的一种手段 在参与开源项目代码贡献时 xff0c 通常不会直接获得源代码仓库的Developer权限 这点和一般公司开发不太一样 xff0c 公司一般都
  • 在OK6410上运行QT程序找不到libQtGui.so.4的解决

    想在OK6410上运行自己经过交叉编译的QT程序 xff0c OK6410上烧写的是光盘所带的Linux系统 xff0c 运行程序时出现以下现象 xff1a qt server error while loading shared libr
  • Docker的安装

    这篇文章原文是我2016年10月份写的 xff0c 当时在研究docker xff0c 上周六不经意翻了出来 xff0c 由于3月换工作 xff0c 新环境比较忙 xff0c 没时间写些东西 xff0c 所以先把之前写的东西放一下 xff0
  • CRAZEPONY飞控学习(一)

    在不久前曾研究过最近最为流行的开源飞控Pixhawk的源代码 xff0c 但是由于在这之前没有接触过操作系统这个概念 xff0c 在不知道代码执行流程的情况下看了几个星期的时间 xff0c 脑子里还是一团乱 xff0c 所以决定还是从裸机开
  • PHP常用的设计模式

    php常用的设计模式 xff1a 1 单例模式 xff08 构造方法私有化 xff0c 对外提供实例化对象的静态调用方法 xff09 class Site span class token punctuation span public s
  • 003.多线程-主线程、守护线程、用户线程的区别

    对于线程的分类 xff0c 我们可以简单划分为 xff1a 主线程 xff08 每个进程只有一个主线程 xff09 子线程 主线程 xff1a main方法 子线程 xff1a 非主线程皆是子线程 子线程中可以简单划分为 xff1a 守护线
  • Ubuntu中在当前目录下打开终端

    在Ubuntu中 xff0c 打开终端可以通过Ctrl 43 Alt 43 T来打开 xff0c 但其打开的是 下的 xff0c 如果进入指定的目录 xff0c 便需要通过cd命令来进行切换 xff0c 故本文提供一个可以通过鼠标右键来在当
  • C语言经典面试题100道(校对详解版)

    题目非本人整理 xff0c 转载于https blog csdn net qq 42613510 article details 81225935 做了校对与详解 xff0c 方便大家参考 最后编程答案自己做的 xff0c 还没写完 xff
  • 浅谈FreeRTOS任务启动与切换流程

    一个轻量级操作系统最核心的地方就在于任务的执行与切换 xff0c 像FreeRTOS和ucOS 在任务启动与切换方面都差不多 xff0c 本文主要从枝干而省去了所有细枝末节让你最快的了解操作系统的任务创建与切换 以正点原子FreeRTOS移
  • 状态机编程思维学习笔记(C语言)

    前言 不摸鱼摆烂的第一天 目录 前言C语言面对对象特性引入函数指针结构体中套用函数指针宏定义中 纯替换 状态机概念状态机实现后文 C语言面对对象特性引入 众所周知 xff0c C 43 43 是由C语言编写而成 xff0c 因此 xff0c
  • stm32串口中断收发数据环形缓冲区的设计

    Function Name USART2 IRQHandler Description This function handles USART2 global interrupt request Input None Output None
  • 多线程的守护线程和等待线程结束方法

    守护线程的含义是 xff1a 如果当前运行的所有线程都是守护线程 xff0c 则程序直接结束 package thread 64 ClassName Test7 64 Author 瞿肖 64 Date 2022 7 11 14 32 pu
  • 用数学规划的方式求解优化问题

    本文介绍如何用数学语言对实际中的优化问题进行建模 通过建立数学模型 我们利用现成的求解器可以便捷地计算出最优解 或可行解 运输问题 考虑三个粮食储量分别是100 200 300的仓库 单位 吨 下文省略 我们需要把粮食运送给4个客户 其需求