1背景紹介
1.1
工場立地の問題オペレーションズリサーチにおける典型的な問題の1つであり、工場建設費、生産量、製品輸送費、現地需要などの要素を包括的に考慮したものです。企業の生産および運用活動を完了するために、適切なロケーション計画およびロジスティクス輸送計画を策定します。この問題の研究モデルは非常に一般的な適応性を持っており、ロジスティクスの分野だけでなく、生産や生活、さらには軍隊でも使用されています。
1.2
シミュレーテッドアニーリングアルゴリズムシミュレーテッドアニーリングアルゴリズム(Simulated Annealing、SA)は、固体アニーリングとの類似性から導き出され、メトロポリス基準(新しい状態を確率で受け入れる)が受け入れ基準として使用されます。いわゆる固体アニーリングは、固体の低エネルギー格子状態を得るための熱処理プロセスです。まず、固体を加熱して完全に無秩序な液体状態に溶かします。このとき、粒子は自由運動状態になり、その後徐々に温度が下がり、粒子運動が徐々に秩序化する傾向があります。結晶化の程度、粒子の動きは格子の周りになりますポイントのわずかな振動により、液体は固化して固体の結晶になります。1983年、カークパトリックは固体アニーリングの原理を最大限に活用し、シミュレーテッドアニーリングアルゴリズムを提案しました。アルゴリズムのコアアイデアは、固体アニーリングプロセスをシミュレートし、最適化問題を解決する際の強力な堅牢性、グローバル性、および適用性を示しています。以下は、アニーリングアルゴリズムの要素と固体アニーリングプロセスの間の大まかな対応です。シミュレーテッドアニーリングアルゴリズムの詳細については、こちらをご覧ください。
固体アニーリング | アニーリングアルゴリズム |
---|---|
粒子の状態i | 最適化された状態xの解 |
最低エネルギー状態 | 最適なソリューション |
システムエネルギー | 目的関数 |
温度 | 制御パラメーター(新しいソリューションを受け入れる確率を決定します) |
一定温度で熱平衡になりがち | 新しいソリューションを生成する—判断(受け入れる、破棄する) |
2問題の説明
個別の工場の場所の問題を例にとると、m個の配送センターに商品を配送するn個の工場があり、各配送センターの需要はbj、j = 1,2、...、mであるとします。配送センターのニーズを満たすことを前提として、輸送製品を製造するために工場の任意の組み合わせを選択できます。特定の工場を選択した場合、固定費di(建設費または運営費として理解できる)i = 1,2、... nが必要であり、各工場の生産能力はaiであるとします。工場iから配送センターjまでの輸送単価がcijの場合、配送センターの需要を満たしながら総コストを最小限に抑えるために、ロケーションプラン(どの工場を選択するか)と輸送プランをどのように策定しますか?
3 Pythonコード設計
3.1パラメータ設定
5つの工場(AE)から10の配送センター(0-9)で構成される工場の場所の問題を考慮して、パラメータ設定は次のとおりです。
(1)各工場の供給能力、地域の需要、および建設/固定運用コスト:
supply_capacity = [4500, 3500, 5000, 3000, 2500]
demand = [729, 630, 321, 293, 251, 573, 207, 732, 481, 783]
fix_cost_lst = [304, 281, 459, 292, 241]
(2)運賃表(「transcost.csv」という名前のファイルに保存されます)
A B C D E
0 9 2 3 7 3
1 5 1 10 6 5
2 6 4 5 3 6
3 8 6 7 3 4
4 8 7 5 6 8
5 1 7 1 5 4
6 9 1 8 10 10
7 8 5 5 3 7
8 4 10 8 5 8
9 3 5 10 7 3
(3)初期サイト選定計画をランダムに作成する
location = np.random.randint(0,2,5)
3.2プログラムコード
コードは主に2つの主要なモジュールに分けられます。1つはサイト選択計画に従って最小総コストを計算することであり、もう1つはアニーリングアルゴリズムを使用してサイト選択計画を最適化することです。
(1)まず、必要なパッケージをインポートし、データを読み取り、初期パラメータを設定します。
import pandas as pd
import numpy as np
import pulp
import math
import matplotlib.pyplot as plt
data = pd.read_csv(r'transcost.csv')
demand = [729, 630, 321, 293, 251, 573, 207, 732, 481, 783]
supply_capacity = [4500, 3500, 5000, 3000, 2500]
fix_cost_lst = [304, 281, 459, 292, 241]
dic = {
0:'A',1:'B',2:'C',3:'D',4:'E'} #建立字典方便后面通过DataFrame组合相应的运价表
(2)反復過程での用地選定計画の変更は、部分的にランダムな工程であり、この過程で、需要に応えられない場合がありますが、その際、そのような計画を調整する必要があります。条件を満たしていない:すべての場合需要が選択した工場の総供給量よりも多い場合、需要を満たすことができるまで、選択されていない工場がランダムに追加されます。
#计算选址方案的总供应量
def Cal_total_capacity(location,capacity):
total_capacity_lst = [capacity[i] for i in range(len(location)) if location[i] == 1]
return sum(total_capacity_lst)
# 对不满足条件的选址方案进行修正
def fixpop(location,capacity,demand):
while sum(demand) > Cal_total_capacity(location, capacity):
ind = np.random.randint(0,len(location))
if location[ind] == 0:
location[ind] = 1
return location
(3)ロケーションスキームの組み合わせに従って、対応する運賃表を生成します。ロケーションプランが[1,0,1,1,0]の場合は、それに応じてデータの列['A'、 'C'、 'D']を選択し、それをこのプランの運賃表に結合します。
def Get_Translist(location,data,dic):
transcost = [list(data[dic[i]]) for i in range(len(location)) if location[i] == 1]
return transcost
(4)立地計画とそれに対応する運賃表を使用して、パルプを使用して最適な輸送計画を計算し始めます。関数はここ(https://www.jianshu.com/p/9be417cbfebb)のコードを参照することです。私はこの部分を輸送問題を解決するためのパッケージとして理解しています。コードロジックを理解する必要はありません。元のコードを直接変更します。輸送の問題を解決するために使用されます。
def transportation_problem(costs, x_max, y_max):
row = len(costs)
col = len(costs[0])
prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMinimize)
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)]}
(5)条件を満たした特定のサイト選択計画の下で、最小コスト(輸送コストと固定費を含む)を計算します。
def Cal_cost(location):
pop = fixpop(location,supply_capacity,demand) #对原来的选址方案进行检验修正,返回处理过的选址方案 pop
fix_cost = 0
capacity_new = []
for i in range(len(pop)):
if pop[i] == 1:
fix_cost += fix_cost_lst[i] #计算该方案下的固定费用fix_cost
capacity_new.append(supply_capacity[i]) #capacity_new是选址方案pop对应的供应能力列表
translist = Get_Translist(pop, data, dic) #得到运价表
costs = np.array(translist)
max_plant = capacity_new
max_cultivation = demand
res = transportation_problem(costs, max_plant, max_cultivation) #求解
return res['var'],res["objective"] + fix_cost #返回的第一个值是pop的最优运输方案,第二个值是pop的最小总费用
Cal_cost(location)関数は、最初の部分の最終目標であり、ロケーションスキーム(ロケーション)を入力して、最小の総コストを出力できます。ここに書かれているのは、最初の部分(ロケーションプランに基づく総コストの計算)コードです。次に、アニーリングの準備を開始します。
(6)初期アニーリング温度を取得します。現在、初期温度を取得するための統一されたルールはありません。ここでは、他のブログで初期温度を取得する方法を参照して、より適切な初期温度を取得できます。np.random.permutation()関数は、リストの並べ替えをランダムにシャッフルするために使用されます。
def GetInitialTem(location):
cost1 = 0 ; cost2 = 0 ; dif = 0
for i in range(10):
location1 = list(np.random.permutation(location))
location2 = list(np.random.permutation(location))
cost1 = Cal_cost(location1)[1]
cost2 = Cal_cost(location2)[1]
difNew = abs(cost1 - cost2)
if difNew >dif:
dif = difNew
pr = 0.5
T0 = dif / pr
return T0
(7)外乱がランダムに生成され、新しい解が得られます。摂動法は、0-1交換の場所の値をランダムに選択し、新しい場所の計画に戻ることです。
def Disturbance(location_old):
location_new = location_old.copy()
ind = np.random.randint(0,len(location_new))
if location_new[ind] == 0:
location_new[ind] = 1
else:
location_new[ind] = 0
return location_new
(8)新しい解を受け入れるかどうかを判断するために使用される受け入れ基準関数は、アルゴリズム全体の鍵です。関数の入力deltaEは、新しい値(ValueNew)と古い値(ValueOld)の差を参照し、Tは現在の温度です。deltaE <0の場合、新しいソリューションが古いソリューションよりも優れていることを意味し、関数は1を返します。これは、新しいソリューションが受け入れられることを意味します。deltaE> 0(新しいソリューションが古いソリューションよりも悪い)の場合、新しい解は(確率>乱数)の確率で受け入れられます。初期温度が高く、確率が高く、新しい解を受け入れる可能性が高くなります。局所最適解から飛び出して早期成熟を防ぐことができますが、温度が徐々に下がると、アニーリングプロセスは徐々に安定し、劣った解を受け入れる確率それが小さくなると、結果は最適化問題の最適解にゆっくりと近づきます。
def Judge(deltaE,T):
if deltaE < 0:
return 1
else:
probility = math.exp(-deltaE/T)
if probility > np.random.random():
return 1
else:
return 0
この時点で、アニーリングプロセスの2番目の部分も準備ができています。次のステップは、コードの主な機能部分を設定することです。
location = np.random.randint(0,2,5) #随机生成初始选址方案
ValueOld = Cal_cost(location)[1] #计算初始方案的最小费用
count = 0 #计数器
record_value = [] #空列表,用来储存每一个被接受的解
tmp = GetInitialTem(location) #初始温度
tmp_min = 1e-5 #最小温度,和计数器一起用于判定退火过程的结束点
alpha = 0.98 #控制退火过程的快慢,即每产生一次较优解,温度下降(0.02*当前温度)
アニーリングを開始します。
while (tmp > tmp_min and count < 2000):
location_n = Disturbance(location) #根据旧的选址方案产生新的选址方案
location_new = fixpop(location_n, supply_capacity, demand) #对新的选址方案进行检验和修正
ValueNew = Cal_cost(location_new)[1] #计算新选址方案的最小总费用
deltaE = ValueNew - ValueOld #比较新旧方案的费用
if Judge(deltaE, tmp) == 1: #如果接受新解
location = location_new #更新选址方案和最小总费用并记录
#print(ValueNew) #可以用来监视程序执行过程中的结果。
#否则代码运行时间较长,我一直怀疑电脑卡住了。。。
ValueOld = ValueNew
record_value.append(ValueNew)
if deltaE < 0 :
tmp = tmp * alpha #如果生成了一个较优解,则退火,温度下降
else:
count += 1
#打印结果
print('='*80,'\n',
'The minvalue is: {}'.format(min(record_value)),'\n',
'The best location is: {}'.format(location),'\n',
'The best transport schedule is: {}'.format(Cal_cost(location)[0]),'\n',
'='*80)
アニーリングプロセスの視覚化
x = len(record_value)
index = [i+1 for i in range(x)]
plt.plot(index,record_value)
plt.xlabel('iterations')
plt.ylabel('minvalue')
plt.title('Simulated Annealing')
4操作結果:
================================================================================
The minvalue is: 13562.0
The best location is: [1 1 0 1 0]
The best transport schedule is:
[[0.0, 0.0, 0.0, 0.0, 0.0, 573.0, 0.0, 0.0, 481.0, 783.0],
[729.0, 630.0, 0.0, 0.0, 0.0, 0.0, 207.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 321.0, 293.0, 251.0, 0.0, 0.0, 732.0, 0.0, 0.0]]
================================================================================
5いくつかのアイデア
現在、私はこの問題を解決するために、シミュレーテッドアニーリング、タブーサーチ、遺伝的アルゴリズムなど、さまざまなヒューリスティックアルゴリズムを試しました。解法プロセスに関する限り、タブーサーチは最適解を得るのに最も速く、最適解は10ステップ以内で見つけることができます。理由はおそらく検索する「山」が少ないためですが、解としてスケールが大きくなり、近傍領域が大きくなると、タブーサーチを解くプロセスが非常に長くなる可能性があります。個人的には、アニーリングアルゴリズムの方が用途が広いと思います(自分で試しましたが、アニーリングアルゴリズムはTSP、VRPで満足のいく結果を得ることができます) 、CVRP、VRPTWなど。)の結果、強力なロバスト性もありますが、この例のアニーリングプロセスは非常に長いです(1つは、理解のエルゴディシティは増加しますが、アルファ= 0.98が大きいためですが、収束速度が影響を受けます。2番目はCal_cost(location)が他の多くの関数を呼び出すためである可能性があり、目的関数値を取得するプロセスは少し複雑です)が、時間に対するソリューションスケールの影響はタブーサーチの影響よりも小さいはずです。 ;遺伝的アルゴリズムはグループ最適化アルゴリズムであり、原理はより複雑で、コードの記述は非常に長いです。最適化速度は理想的ではありません。
最後に、Xiaobaiが初めてブログを書いたとき、彼はPythonに精通していなかったので、アドバイスを求めるという謙虚な態度で段階的に書いていました。コードが簡潔でなく、標準化されていない領域があるはずです。アルゴリズムの理解にも偏りがあります。また、大物が悟りに飽きないことを願っています。次に、タブーサーチを使って問題を解決する方法をデモンストレーションする予定です。時間があれば、別の記事を書きます。