Python数学建模学习模拟退火算法约束条件处理示例解析
作者:youcans 发布时间:2022-06-16 19:19:20
1、最优化与线性规划
最优化问题的三要素是决策变量、目标函数和约束条件。
线性规划(Linear programming),是研究线性约束条件下线性目标函数的极值问题的优化方法,常用于解决利用现有的资源得到最优决策的问题。
简单的线性规划问题可以用 Lingo软件求解,Matlab、Python 中也有求解线性规划问题的库函数或求解器,很容易学习和使用,并不需要用模拟退火算法。但是,由一般线性规划问题所衍生的整数规划、混合规划、0/1规划、二次规划、非线性规划、组合优化问题,则并不是调用某个库函数都能处理的。而模拟退火算法在很多复杂问题中具有较好的适应性,可以作为一种入门的通用智能算法来学习。
也就是说,如果只是处理线性规划问题,就不要用模拟退火算法了。但如果是现有方法无法处理的复杂优化问题,或者对某类、某个优化问题你不知道用什么方法处理了,这时用模拟退火算法还是能解决的。
本文使用惩罚函数法,分析模拟退火算法处理线性规划问题,相关内容也适用于非线性规划问题。
2、模拟退火算法处理约束条件
线性规划问题是约束优化问题,而模拟退火算法则更适合处理无约束优化问题。对于优化问题中的约束条件,模拟退火算法有几种常用的处理方法:
1.决策变量取值的上下限约束。
此类约束条件比较容易处理,只要设定初始解、新解在决策变量取值的上下限之间就可以解决。例如:
(1)设置产生新解的随机数的上下限为决策变量的上下限,即 [Xmin, Xmax];
(2)设置产生新解的随机数的上下限为当前解与决策变量的上下限,即 [Xnow, Xmax];
(3)通过条件判断,当新解超出决策变量上下限,则令其取上下限,即 xNew = max(min(xNew, xMax), xMin)。当然,这些处理方式,都会影响随机数的概率分布,因而也影响模拟退火算法的优化性能,在此不做深入讨论。
2.检验法处理不等式约束问题。
在模拟退火算法的迭代过程中,将每次产生的新解代入每个不等式约束函数,判断是否满足约束条件;如果新解不满足约束条件,则舍弃这个新解,返回重新产生一个新解进行检验,直到产生的新解满足全部约束条件为止。这个方法的思路简单,每次迭代都在可行域内进行,但是对于约束条件众多、苛刻的复杂问题,多次产生的新解都不能满足约束条件,会使计算时间很长,甚至停滞不前。
3.消元法处理等式约束问题。
对于等式约束,很难通过随机产生的新解满足约束条件,通常不能使用检验法处理。消元法是通过解方程将等式约束中的某个决策变量表示为其它决策变量的线性关系后,代入目标函数和不等式约束条件中,从而消去该约束条件。消元法不仅解决了等式约束问题,而且减少了决策变量的数量,从而有效简化了优化问题的复杂度,是一举两得的处理方法。但是,对于非线性等式约束,求解非线性方程组也是非常困难的,消元法并不是普遍都能适用的。
4.更为通用的处理约束条件的方法是惩罚函数法,以下进行介绍。YouCans, XUPT
3、惩罚函数法
惩罚函数法是一类常用的处理约束条件的技术,在模拟退火算法中处理约束条件非常有效。方法的思想是将约束条件转化为惩罚函数,附加在原有的目标函数上构造新的目标函数;当不满足约束条件时,通过惩罚函数使新的目标函数变差而被舍弃。
惩罚函数法有外点法和内点法。外点法对可行域外的点(即不满足约束的点)施加惩罚,对可行域内部的点不惩罚,从而使迭代点向可行域D逼近。 内点法是在可行域内部进行搜索,约束边界起到类似围墙的作用,使目标函数无法穿过,就把搜索点限制在可行域内了,因此只适用于不等式约束。
4、数模案例
虽然对于线性规划问题并不推荐使用模拟退火算法求解。但为了便于理解,本文仍使用之前的线性规划问题作为处理约束条件的案例。对于非线性规划问题,以及非线性约束问题,处理方法都是类似的,将在后续进行介绍。
4.1 问题描述:
某厂生产甲乙两种饮料,每百箱甲饮料需用原料6千克、工人10名,获利10万元;每百箱乙饮料需用原料5千克、工人20名,获利9万元。
今工厂共有原料60千克、工人150名,又由于其他条件所限甲饮料产量不超过8百箱。
(1)问如何安排生产计划,即两种饮料各生产多少使获利最大?
4.2 问题建模:
决策变量:
x1:甲饮料产量(单位:百箱)
x2:乙饮料产量(单位:百箱)
目标函数:
max fx = 10x1 + 9x2
约束条件:
6x1 + 5x2 <= 60
10x1 + 20x2 <= 150
取值范围:
给定条件:x1, x2 >= 0,x1 <= 8
推导条件:由 x1,x2>=0 和 10*x1+20*x2<=150 可知:0<=x1<=15;0<=x2<=7.5
因此,0 <= x1<=8,0 <= x2<=7.5
4.3 惩罚函数法求解约束优化问题:
构造惩罚函数:
p1 = (max(0, 6*x1+5*x2-60))**2
p2 = (max(0, 10*x1+20*x2-150))**2
说明:如存在等式约束,例如:x1 + 2*x2 = m,也可以转化为惩罚函数:
p3 = (x1+2*x2-m)**2
P(x) = p1 + p2 + …
构造增广目标函数:
L(x,m(k)) = min(fx) + m(k)*P(x)
m(k):惩罚因子,随迭代次数 k 逐渐增大
在模拟退火算法中,m(k) 随外循环迭代次数逐渐增大,但在内循环中应保持不变。
5、模拟退火算法 Python 程序
惩罚函数法求解约束优化线性规划问题
# 模拟退火算法 程序:惩罚函数法求解线性规划问题
# Program: SimulatedAnnealing_v2.py
# Purpose: Simulated annealing algorithm for function optimization
# v2.0: 使用惩罚函数法处理约束问题
# Copyright 2021 YouCans, XUPT
# Crated:2021-05-01
# = 关注 Youcans,分享原创系列 https://blog.csdn.net/youcans =
# -*- coding: utf-8 -*-
import math # 导入模块
import random # 导入模块
import pandas as pd # 导入模块 YouCans, XUPT
import numpy as np # 导入模块 numpy,并简写成 np
import matplotlib.pyplot as plt
from datetime import datetime
# 子程序:定义优化问题的目标函数
def cal_Energy(X, nVar, mk): # m(k):惩罚因子,随迭代次数 k 逐渐增大
p1 = (max(0, 6*X[0]+5*X[1]-60))**2
p2 = (max(0, 10*X[0]+20*X[1]-150))**2
fx = -(10*X[0]+9*X[1])
return fx+mk*(p1+p2)
# 子程序:模拟退火算法的参数设置
def ParameterSetting():
cName = "funcOpt" # 定义问题名称 YouCans, XUPT
nVar = 2 # 给定自变量数量,y=f(x1,..xn)
xMin = [0, 0] # 给定搜索空间的下限,x1_min,..xn_min
xMax = [8, 7.5] # 给定搜索空间的上限,x1_max,..xn_max
tInitial = 100.0 # 设定初始退火温度(initial temperature)
tFinal = 1 # 设定终止退火温度(stop temperature)
alfa = 0.98 # 设定降温参数,T(k)=alfa*T(k-1)
meanMarkov = 100 # Markov链长度,也即内循环运行次数
scale = 0.5 # 定义搜索步长,可以设为固定值或逐渐缩小
return cName, nVar, xMin, xMax, tInitial, tFinal, alfa, meanMarkov, scale
# 模拟退火算法
def OptimizationSSA(nVar,xMin,xMax,tInitial,tFinal,alfa,meanMarkov,scale):
# ====== 初始化随机数发生器 ======
randseed = random.randint(1, 100)
random.seed(randseed) # 随机数发生器设置种子,也可以设为指定整数
# ====== 随机产生优化问题的初始解 ======
xInitial = np.zeros((nVar)) # 初始化,创建数组
for v in range(nVar):
# random.uniform(min,max) 在 [min,max] 范围内随机生成一个实数
xInitial[v] = random.uniform(xMin[v], xMax[v])
# 调用子函数 cal_Energy 计算当前解的目标函数值
fxInitial = cal_Energy(xInitial, nVar, 1) # m(k):惩罚因子,初值为 1
# ====== 模拟退火算法初始化 ======
xNew = np.zeros((nVar)) # 初始化,创建数组
xNow = np.zeros((nVar)) # 初始化,创建数组
xBest = np.zeros((nVar)) # 初始化,创建数组
xNow[:] = xInitial[:] # 初始化当前解,将初始解置为当前解
xBest[:] = xInitial[:] # 初始化最优解,将当前解置为最优解
fxNow = fxInitial # 将初始解的目标函数置为当前值
fxBest = fxInitial # 将当前解的目标函数置为最优值
print('x_Initial:{:.6f},{:.6f},\tf(x_Initial):{:.6f}'.format(xInitial[0], xInitial[1], fxInitial))
recordIter = [] # 初始化,外循环次数
recordFxNow = [] # 初始化,当前解的目标函数值
recordFxBest = [] # 初始化,最佳解的目标函数值
recordPBad = [] # 初始化,劣质解的接受概率
kIter = 0 # 外循环迭代次数,温度状态数
totalMar = 0 # 总计 Markov 链长度
totalImprove = 0 # fxBest 改善次数
nMarkov = meanMarkov # 固定长度 Markov链
# ====== 开始模拟退火优化 ======
# 外循环,直到当前温度达到终止温度时结束
tNow = tInitial # 初始化当前温度(current temperature)
while tNow >= tFinal: # 外循环,直到当前温度达到终止温度时结束
# 在当前温度下,进行充分次数(nMarkov)的状态转移以达到热平衡
kBetter = 0 # 获得优质解的次数
kBadAccept = 0 # 接受劣质解的次数
kBadRefuse = 0 # 拒绝劣质解的次数
# ---内循环,循环次数为Markov链长度
for k in range(nMarkov): # 内循环,循环次数为Markov链长度
totalMar += 1 # 总 Markov链长度计数器
# ---产生新解
# 产生新解:通过在当前解附近随机扰动而产生新解,新解必须在 [min,max] 范围内
# 方案 1:只对 n元变量中的一个进行扰动,其它 n-1个变量保持不变
xNew[:] = xNow[:]
v = random.randint(0, nVar-1) # 产生 [0,nVar-1]之间的随机数
xNew[v] = xNow[v] + scale * (xMax[v]-xMin[v]) * random.normalvariate(0, 1)
# random.normalvariate(0, 1):产生服从均值为0、标准差为 1 的正态分布随机实数
xNew[v] = max(min(xNew[v], xMax[v]), xMin[v]) # 保证新解在 [min,max] 范围内
# ---计算目标函数和能量差
# 调用子函数 cal_Energy 计算新解的目标函数值
fxNew = cal_Energy(xNew, nVar, kIter)
deltaE = fxNew - fxNow
# ---按 Metropolis 准则接受新解
# 接受判别:按照 Metropolis 准则决定是否接受新解
if fxNew < fxNow: # 更优解:如果新解的目标函数好于当前解,则接受新解
accept = True
kBetter += 1
else: # 容忍解:如果新解的目标函数比当前解差,则以一定概率接受新解
pAccept = math.exp(-deltaE / tNow) # 计算容忍解的状态迁移概率
if pAccept > random.random():
accept = True # 接受劣质解
kBadAccept += 1
else:
accept = False # 拒绝劣质解
kBadRefuse += 1
# 保存新解
if accept == True: # 如果接受新解,则将新解保存为当前解
xNow[:] = xNew[:]
fxNow = fxNew
if fxNew < fxBest: # 如果新解的目标函数好于最优解,则将新解保存为最优解
fxBest = fxNew
xBest[:] = xNew[:]
totalImprove += 1
scale = scale*0.99 # 可变搜索步长,逐步减小搜索范围,提高搜索精度
# ---内循环结束后的数据整理
# 完成当前温度的搜索,保存数据和输出
pBadAccept = kBadAccept / (kBadAccept + kBadRefuse) # 劣质解的接受概率
recordIter.append(kIter) # 当前外循环次数
recordFxNow.append(round(fxNow, 4)) # 当前解的目标函数值
recordFxBest.append(round(fxBest, 4)) # 最佳解的目标函数值
recordPBad.append(round(pBadAccept, 4)) # 最佳解的目标函数值
if kIter%10 == 0: # 模运算,商的余数
print('i:{},t(i):{:.2f}, badAccept:{:.6f}, f(x)_best:{:.6f}'.\
format(kIter, tNow, pBadAccept, fxBest))
# 缓慢降温至新的温度,降温曲线:T(k)=alfa*T(k-1)
tNow = tNow * alfa
kIter = kIter + 1
fxBest = cal_Energy(xBest, nVar, kIter) # 由于迭代后惩罚因子增大,需随之重构增广目标函数
# ====== 结束模拟退火过程 ======
print('improve:{:d}'.format(totalImprove))
return kIter,xBest,fxBest,fxNow,recordIter,recordFxNow,recordFxBest,recordPBad
# 结果校验与输出
def ResultOutput(cName,nVar,xBest,fxBest,kIter,recordFxNow,recordFxBest,recordPBad,recordIter):
# ====== 优化结果校验与输出 ======
fxCheck = cal_Energy(xBest, nVar, kIter)
if abs(fxBest - fxCheck)>1e-3: # 检验目标函数
print("Error 2: Wrong total millage!")
return
else:
print("\nOptimization by simulated annealing algorithm:")
for i in range(nVar):
print('\tx[{}] = {:.6f}'.format(i,xBest[i]))
print('\n\tf(x):{:.6f}'.format(cal_Energy(xBest,nVar,0)))
return
def main(): # YouCans, XUPT
# 参数设置,优化问题参数定义,模拟退火算法参数设置
[cName, nVar, xMin, xMax, tInitial, tFinal, alfa, meanMarkov, scale] = ParameterSetting()
# print([nVar, xMin, xMax, tInitial, tFinal, alfa, meanMarkov, scale])
# 模拟退火算法
[kIter,xBest,fxBest,fxNow,recordIter,recordFxNow,recordFxBest,recordPBad] \
= OptimizationSSA(nVar,xMin,xMax,tInitial,tFinal,alfa,meanMarkov,scale)
# print(kIter, fxNow, fxBest, pBadAccept)
# 结果校验与输出
ResultOutput(cName, nVar,xBest,fxBest,kIter,recordFxNow,recordFxBest,recordPBad,recordIter)
if __name__ == '__main__':
main()
6、运行结果
Optimization by simulated annealing algorithm:
x[0] = 6.577964
x[1] = 4.111469
f(x):-102.782857
参考文献:
(1)胡山鹰,陈丙珍,非线性规划问题全局优化的模拟退火法,清华大学学报,1997,37(6):5-9
(2)李歧强,具有约束指导的模拟退火算法,系统工程,2001,19(3):49-55
来源:https://blog.csdn.net/youcans/article/details/116371882
猜你喜欢
- 适用环境: PHP5.2.x / mysql 5.0.xclass Mysql { priva
- 项目开发中,前端在配置后端api域名时很困扰,常常出现:本地开发环境: api-dev.demo.com测试环境: api-test.dem
- 前言favicon.ico是网站的图标也是网站的头像,简单来说,就是让我们的网站更加好看。本文主要给大家介绍了关于优雅处理Django中fa
- Javascript脚本获取form和input内容的方法随着js的发展,许多的网页数据处理完全可以由js脚本解决,而不需要发送到服务器这里
- 一. pprint美观打印数据结构pprint模块包含一个“美观打印机”,用于生成数据结构的一个美观的视图。格式化工具会生成数据结构的一些表
- 1 丰富的二维动画/图形和视音频表现 Rich 2D animation/graphics with audio and video这点毋庸
- 在python中,用于数组拼接的主要来自numpy包,当然pandas包也可以完成。而,numpy中可以使用append和concatena
- 上拉加载以及下拉刷新都是移动端很常见的功能,在搜索或者一些分类列表页面常常会用到。跟横向滚动一样,我们还是采用better-scroll这个
- sqrt()方法返回x的平方根(x>0)。语法以下是sqrt()方法的语法:import mathmath.sqrt( x
- 前几天一直在寻找能够输出python函数运行时最大内存消耗的方式,看了一堆的博客和知乎,也尝试了很多方法,最后选择使用memory_prof
- 前言最近无意间发现mysql的coalesce,又正好有时间,就把mysql中coalesce()的使用技巧总结下分享给大家,下面来一起看看
- 如何提高Request集合的使用效率?以加快程序处理速度: strTitle=Request.Form("Title&q
- 前言诶?这有一个按钮(~ ̄▽ ̄)~,这是一个在html中实现的具有金属质感并且能镜面反射的按钮~效果电脑效果手机效果说明主要思路是使用 na
- 摘要:Oracle和微软都是数据库方面的大厂商,采用两家的产品的企业也不少。今天这篇文章为大家对比Oracle和SQLServer的镜像。标
- 一、检查系统版本[root@node ~]# cat /etc/os-release NAME="CentOS Linux&quo
- #!/bin/sh#code by scpman#功能:检查并修复mysql数据库表#将此脚本加到定时中,脚本执行时,等会读库,列出要修复的
- 本篇文章适合css新手学习,对于已经掌握了css的朋友们也可以通过本片文章来复习知识。作者通过实践,认为在有些情况下css的代码是可以更加简
- tkinter是python的标准Tk GUI工具包的接口,在windows下如果你安装的python3,那在安装python的时候,就已经
- 之前写了一篇flask开发环境搭建,今天继续,进行一个实战小项目-blog系统。blog系统很简单,只有一个页面,然后麻雀虽小五脏俱全。这里
- 本文实例讲述了Python实现文件内容批量追加的方法。分享给大家供大家参考,具体如下:#coding:utf-8import os#----